Page MenuHomeDevelopment

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/.editorconfig b/.editorconfig
index 482764928..8ac1dae50 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,287 +1,287 @@
# 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 = true
dotnet_sort_system_directives_first = true
-file_header_template = This file is part of the DisCatSharp project, based off DSharpPlus.\n\nCopyright (c) 2021-2022 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.
+file_header_template = This file is part of the DisCatSharp project, based off DSharpPlus.\n\nCopyright (c) 2021-2023 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_readonly_fields_convention.severity = error
dotnet_naming_rule.private_static_readonly_fields_convention.symbols = private_static_fields_readonly
dotnet_naming_rule.private_static_readonly_fields_convention.style = private_static_camel_case
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.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
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.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 = constant_style
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.private_props_not_allowed.severity = error
dotnet_naming_rule.private_props_not_allowed.symbols = private_prop
dotnet_naming_rule.private_props_not_allowed.style = constant_style
# 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_static_fields_readonly.applicable_kinds = field
dotnet_naming_symbols.private_static_fields_readonly.applicable_accessibilities = private
dotnet_naming_symbols.private_static_fields_readonly.required_modifiers = static, readonly
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.private_prop.applicable_kinds = property
dotnet_naming_symbols.private_prop.applicable_accessibilities = private
dotnet_naming_symbols.private_prop.required_modifiers =
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, property
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.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/ApplicationCommandsConfiguration.cs b/DisCatSharp.ApplicationCommands/ApplicationCommandsConfiguration.cs
index 927b041b6..dde80e068 100644
--- a/DisCatSharp.ApplicationCommands/ApplicationCommandsConfiguration.cs
+++ b/DisCatSharp.ApplicationCommands/ApplicationCommandsConfiguration.cs
@@ -1,121 +1,121 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.DependencyInjection;
namespace DisCatSharp.ApplicationCommands;
/// <summary>
/// A configuration for a <see cref="ApplicationCommandsExtension"/>
/// </summary>
public class ApplicationCommandsConfiguration
{
/// <summary>
/// <para>Sets the service provider.</para>
/// <para>Objects in this provider are used when instantiating application command modules.</para>
/// <para>This allows passing data around without resorting to static members.</para>
/// <para>Defaults to <see langword="null"/>.</para>
/// </summary>
public IServiceProvider ServiceProvider { internal get; set; }
/// <summary>
/// <para>This option enables the default help command.</para>
/// <para>Disabling this will allow you to make your own help command.</para>
/// <para>Defaults to <see langword="true"/>.</para>
/// </summary>
public bool EnableDefaultHelp { internal get; set; } = true;
/// <summary>
/// This option enables the localization feature.
/// <para>Defaults to <see langword="false"/>.</para>
/// </summary>
public bool EnableLocalization { internal get; set; } = false;
/// <summary>
/// <para>Automatically defer all responses.</para>
/// <note type="note">If you enable this, you can't use CreateResponse. Use EditResponse instead.</note>
/// <para>Defaults to <see langword="false"/>.</para>
/// </summary>
public bool AutoDefer { internal get; set; } = false;
/// <summary>
/// <para>This option informs the module to check through all guilds whether the
/// <see target="_blank" alt="Application Commands Scope" href="https://discord.com/developers/docs/topics/oauth2#shared-resources-oauth2-scopes">application.commands</see> scope is set.</para>
/// <note type="warning">This will take quite a while, when the bot is on more than 1k guilds.</note>
/// <para>Defaults to <see langword="false"/>.</para>
/// </summary>
public bool CheckAllGuilds { internal get; set; } = false;
/// <summary>
/// <para>This option can override the default registration behavior of the module.</para>
/// <note type="warning">
/// <para>It can lead to unexpected behavior of the application commands module.</para>
/// <para>Enable this option only if DisCatSharp support advises you to do so.</para>
/// </note>
/// <para>Defaults to <see langword="false"/>.</para>
/// </summary>
public bool ManualOverride { internal get; set; } = false;
/// <summary>
/// <para>This option increases the debug output of the module.</para>
/// <note type="warning">
/// <para>This is not recommended for production use.</para>
/// <para>Enable this option only if DisCatSharp support advises you to do so.</para>
/// </note>
/// <para>Defaults to <see langword="false"/>.</para>
/// </summary>
public bool DebugStartup { internal get; set; } = false;
/// <summary>
/// <para>>Whether to only generate translations files and abort after that.</para>
/// <para>Defaults to <see langword="false"/>.</para>
/// </summary>
public bool GenerateTranslationFilesOnly { internal get; set; } = false;
/// <summary>
/// Initializes a new instance of the <see cref="ApplicationCommandsConfiguration"/> class.
/// </summary>
/// <param name="provider">The service provider.</param>
[ActivatorUtilitiesConstructor]
public ApplicationCommandsConfiguration(IServiceProvider provider = null)
{
this.ServiceProvider = provider;
}
/// <summary>
/// Creates a new instance of <see cref="ApplicationCommandsConfiguration"/>, copying the properties of another configuration.
/// </summary>
/// <param name="acc">Configuration the properties of which are to be copied.</param>
public ApplicationCommandsConfiguration(ApplicationCommandsConfiguration acc)
{
this.EnableDefaultHelp = acc.EnableDefaultHelp;
this.ServiceProvider = acc.ServiceProvider;
this.DebugStartup = acc.DebugStartup;
this.CheckAllGuilds = acc.CheckAllGuilds;
this.ManualOverride = acc.ManualOverride;
this.AutoDefer = acc.AutoDefer;
this.EnableLocalization = acc.EnableLocalization;
this.GenerateTranslationFilesOnly = acc.GenerateTranslationFilesOnly;
}
}
diff --git a/DisCatSharp.ApplicationCommands/ApplicationCommandsExtension.cs b/DisCatSharp.ApplicationCommands/ApplicationCommandsExtension.cs
index 00b190165..a2a95b229 100644
--- a/DisCatSharp.ApplicationCommands/ApplicationCommandsExtension.cs
+++ b/DisCatSharp.ApplicationCommands/ApplicationCommandsExtension.cs
@@ -1,2136 +1,2135 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Reflection;
-using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using DisCatSharp.ApplicationCommands.Attributes;
using DisCatSharp.ApplicationCommands.Context;
using DisCatSharp.ApplicationCommands.Entities;
using DisCatSharp.ApplicationCommands.Enums;
using DisCatSharp.ApplicationCommands.EventArgs;
using DisCatSharp.ApplicationCommands.Exceptions;
using DisCatSharp.ApplicationCommands.Workers;
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;
/// <summary>
/// A class that handles slash commands for a client.
/// </summary>
public sealed class ApplicationCommandsExtension : BaseExtension
{
/// <summary>
/// A list of methods for top level commands.
/// </summary>
private static List<CommandMethod> s_commandMethods { get; set; } = new();
/// <summary>
/// List of groups.
/// </summary>
private static List<GroupCommand> s_groupCommands { get; set; } = new();
/// <summary>
/// List of groups with subgroups.
/// </summary>
private static List<SubGroupCommand> s_subGroupCommands { get; set; } = new();
/// <summary>
/// List of context menus.
/// </summary>
private static List<ContextMenuCommand> s_contextMenuCommands { get; set; } = new();
/// <summary>
/// List of global commands on discords backend.
/// </summary>
internal static List<DiscordApplicationCommand> GlobalDiscordCommands { get; set; }
/// <summary>
/// List of guild commands on discords backend.
/// </summary>
internal static Dictionary<ulong, List<DiscordApplicationCommand>> GuildDiscordCommands { get; set; }
/// <summary>
/// Singleton modules.
/// </summary>
private static List<object> s_singletonModules { get; set; } = new();
/// <summary>
/// List of modules to register.
/// </summary>
private readonly List<KeyValuePair<ulong?, ApplicationCommandsModuleConfiguration>> _updateList = new();
/// <summary>
/// Configuration for Discord.
/// </summary>
internal static ApplicationCommandsConfiguration Configuration;
/// <summary>
/// Set to true if anything fails when registering.
/// </summary>
private static bool s_errored { get; set; }
/// <summary>
/// Gets a list of registered commands. The key is the guild id (null if global).
/// </summary>
public IReadOnlyList<KeyValuePair<ulong?, IReadOnlyList<DiscordApplicationCommand>>> RegisteredCommands
=> s_registeredCommands;
private static List<KeyValuePair<ulong?, IReadOnlyList<DiscordApplicationCommand>>> s_registeredCommands = new();
/// <summary>
/// Gets a list of registered global commands.
/// </summary>
public IReadOnlyList<DiscordApplicationCommand> GlobalCommands
=> GlobalCommandsInternal;
internal static List<DiscordApplicationCommand> GlobalCommandsInternal = new();
/// <summary>
/// Gets a list of registered guild commands mapped by guild id.
/// </summary>
public IReadOnlyDictionary<ulong, IReadOnlyList<DiscordApplicationCommand>> GuildCommands
=> GuildCommandsInternal;
internal static Dictionary<ulong, IReadOnlyList<DiscordApplicationCommand>> GuildCommandsInternal = new();
/// <summary>
/// Gets the registration count.
/// </summary>
private static int s_registrationCount { get; set; }
/// <summary>
/// Gets the expected count.
/// </summary>
private static int s_expectedCount { get; set; }
/// <summary>
/// Gets the guild ids where the applications.commands scope is missing.
/// </summary>
private List<ulong> _missingScopeGuildIds;
/// <summary>
/// Gets whether debug is enabled.
/// </summary>
internal static bool DebugEnabled { get; set; }
internal static LogLevel ApplicationCommandsLogLevel
=> DebugEnabled ? LogLevel.Debug : LogLevel.Trace;
/// <summary>
/// Gets whether check through all guilds is enabled.
/// </summary>
internal static bool CheckAllGuilds { get; set; }
/// <summary>
/// Gets whether the registration check should be manually overridden.
/// </summary>
internal static bool ManOr { get; set; }
/// <summary>
/// Gets whether interactions should be automatically deffered.
/// </summary>
internal static bool AutoDeferEnabled { get; set; }
/// <summary>
/// Whether this module finished the startup.
/// </summary>
internal bool StartupFinished { get; set; } = false;
/// <summary>
/// Gets the service provider this module was configured with.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>")]
public IServiceProvider Services
=> Configuration.ServiceProvider;
/// <summary>
/// Gets a list of handled interactions. Fix for double interaction execution bug.
/// </summary>
internal static List<ulong> HandledInteractions = new();
/// <summary>
/// Initializes a new instance of the <see cref="ApplicationCommandsExtension"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
internal ApplicationCommandsExtension(ApplicationCommandsConfiguration configuration = null)
{
configuration ??= new ApplicationCommandsConfiguration();
Configuration = configuration;
DebugEnabled = configuration?.DebugStartup ?? false;
CheckAllGuilds = configuration?.CheckAllGuilds ?? false;
ManOr = configuration?.ManualOverride ?? false;
AutoDeferEnabled = configuration?.AutoDefer ?? false;
}
/// <summary>
/// Runs setup.
/// <note type="caution">DO NOT RUN THIS MANUALLY. DO NOT DO ANYTHING WITH THIS.</note>
/// </summary>
/// <param name="client">The client to setup on.</param>
protected internal override void Setup(DiscordClient client)
{
if (this.Client != null)
throw new InvalidOperationException("What did I tell you?");
this.Client = client;
this._slashError = new AsyncEvent<ApplicationCommandsExtension, SlashCommandErrorEventArgs>("SLASHCOMMAND_ERRORED", TimeSpan.Zero, null);
this._slashExecuted = new AsyncEvent<ApplicationCommandsExtension, SlashCommandExecutedEventArgs>("SLASHCOMMAND_EXECUTED", TimeSpan.Zero, null);
this._contextMenuErrored = new AsyncEvent<ApplicationCommandsExtension, ContextMenuErrorEventArgs>("CONTEXTMENU_ERRORED", TimeSpan.Zero, null);
this._contextMenuExecuted = new AsyncEvent<ApplicationCommandsExtension, ContextMenuExecutedEventArgs>("CONTEXTMENU_EXECUTED", TimeSpan.Zero, null);
this._applicationCommandsModuleReady = new AsyncEvent<ApplicationCommandsExtension, ApplicationCommandsModuleReadyEventArgs>("APPLICATION_COMMANDS_MODULE_READY", TimeSpan.Zero, null);
this._applicationCommandsModuleStartupFinished = new AsyncEvent<ApplicationCommandsExtension, ApplicationCommandsModuleStartupFinishedEventArgs>("APPLICATION_COMMANDS_MODULE_STARTUP_FINISHED", TimeSpan.Zero, null);
this._globalApplicationCommandsRegistered = new AsyncEvent<ApplicationCommandsExtension, GlobalApplicationCommandsRegisteredEventArgs>("GLOBAL_COMMANDS_REGISTERED", TimeSpan.Zero, null);
this._guildApplicationCommandsRegistered = new AsyncEvent<ApplicationCommandsExtension, GuildApplicationCommandsRegisteredEventArgs>("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)
{
if (!this.StartupFinished)
await e.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().AsEphemeral(true).WithContent("Attention: This application is still starting up. Application commands are unavailable for now."));
else
await Task.Delay(1);
}
private async Task CatchContextMenuInteractionsOnStartup(DiscordClient sender, ContextMenuInteractionCreateEventArgs e)
{
if (!this.StartupFinished)
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."));
else
await Task.Delay(1);
}
private void FinishedRegistration()
{
this.Client.InteractionCreated -= this.CatchInteractionsOnStartup;
this.Client.ContextMenuInteractionCreated -= this.CatchContextMenuInteractionsOnStartup;
this.StartupFinished = true;
this.Client.InteractionCreated += this.InteractionHandler;
this.Client.ContextMenuInteractionCreated += this.ContextMenuHandler;
}
/// <summary>
/// Cleans the module for a new start of the bot.
/// DO NOT USE IF YOU DON'T KNOW WHAT IT DOES.
/// </summary>
public void CleanModule()
{
this._updateList.Clear();
s_singletonModules.Clear();
s_errored = false;
s_expectedCount = 0;
s_registrationCount = 0;
s_commandMethods.Clear();
s_groupCommands.Clear();
s_contextMenuCommands.Clear();
s_subGroupCommands.Clear();
s_singletonModules.Clear();
s_registeredCommands.Clear();
GlobalCommandsInternal.Clear();
GuildCommandsInternal.Clear();
}
/// <summary>
/// Cleans all guild application commands.
/// <note type="caution">You normally don't need to execute it.</note>
/// </summary>
internal async Task CleanGuildCommandsAsync()
{
foreach (var guild in this.Client.Guilds.Values)
await this.Client.BulkOverwriteGuildApplicationCommandsAsync(guild.Id, Array.Empty<DiscordApplicationCommand>());
}
/// <summary>
/// Cleans the global application commands.
/// <note type="caution">You normally don't need to execute it.</note>
/// </summary>
internal async Task CleanGlobalCommandsAsync()
=> await this.Client.BulkOverwriteGlobalApplicationCommandsAsync(Array.Empty<DiscordApplicationCommand>());
/// <summary>
/// Registers all commands from a given assembly. The command classes need to be public to be considered for registration.
/// </summary>
/// <param name="assembly">Assembly to register commands from.</param>
public void RegisterGuildCommands(Assembly assembly, ulong guildId)
{
var types = assembly.ExportedTypes.Where(xt =>
{
var xti = xt.GetTypeInfo();
return xti.IsModuleCandidateType() && !xti.IsNested;
});
foreach (var xt in types)
this.RegisterGuildCommands(xt, guildId, null);
}
/// <summary>
/// Registers all commands from a given assembly. The command classes need to be public to be considered for registration.
/// </summary>
/// <param name="assembly">Assembly to register commands from.</param>
public void RegisterGlobalCommands(Assembly assembly)
{
var types = assembly.ExportedTypes.Where(xt =>
{
var xti = xt.GetTypeInfo();
return xti.IsModuleCandidateType() && !xti.IsNested;
});
foreach (var xt in types)
this.RegisterGlobalCommands(xt, null);
}
/// <summary>
/// Registers a command class with optional translation setup for a guild.
/// </summary>
/// <typeparam name="T">The command class to register.</typeparam>
/// <param name="guildId">The guild id to register it on.</param>
/// <param name="translationSetup">A callback to setup translations with.</param>
public void RegisterGuildCommands<T>(ulong guildId, Action<ApplicationCommandsTranslationContext> translationSetup = null) where T : ApplicationCommandsModule
=> this._updateList.Add(new KeyValuePair<ulong?, ApplicationCommandsModuleConfiguration>(guildId, new ApplicationCommandsModuleConfiguration(typeof(T), translationSetup)));
/// <summary>
/// Registers a command class with optional translation setup for a guild.
/// </summary>
/// <param name="type">The <see cref="System.Type"/> of the command class to register.</param>
/// <param name="guildId">The guild id to register it on.</param>
/// <param name="translationSetup">A callback to setup translations with.</param>
public void RegisterGuildCommands(Type type, ulong guildId, Action<ApplicationCommandsTranslationContext> translationSetup = null)
{
if (!typeof(ApplicationCommandsModule).IsAssignableFrom(type))
throw new ArgumentException("Command classes have to inherit from ApplicationCommandsModule", nameof(type));
this._updateList.Add(new KeyValuePair<ulong?, ApplicationCommandsModuleConfiguration>(guildId, new ApplicationCommandsModuleConfiguration(type, translationSetup)));
}
/// <summary>
/// Registers a command class with optional translation setup globally.
/// </summary>
/// <typeparam name="T">The command class to register.</typeparam>
/// <param name="translationSetup">A callback to setup translations with.</param>
public void RegisterGlobalCommands<T>(Action<ApplicationCommandsTranslationContext> translationSetup = null) where T : ApplicationCommandsModule
=> this._updateList.Add(new KeyValuePair<ulong?, ApplicationCommandsModuleConfiguration>(null, new ApplicationCommandsModuleConfiguration(typeof(T), translationSetup)));
/// <summary>
/// Registers a command class with optional translation setup globally.
/// </summary>
/// <param name="type">The <see cref="System.Type"/> of the command class to register.</param>
/// <param name="translationSetup">A callback to setup translations with.</param>
public void RegisterGlobalCommands(Type type, Action<ApplicationCommandsTranslationContext> translationSetup = null)
{
if (!typeof(ApplicationCommandsModule).IsAssignableFrom(type))
throw new ArgumentException("Command classes have to inherit from ApplicationCommandsModule", nameof(type));
this._updateList.Add(new KeyValuePair<ulong?, ApplicationCommandsModuleConfiguration>(null, new ApplicationCommandsModuleConfiguration(type, translationSetup)));
}
/// <summary>
/// Fired when the application commands module is ready.
/// </summary>
public event AsyncEventHandler<ApplicationCommandsExtension, ApplicationCommandsModuleReadyEventArgs> ApplicationCommandsModuleReady
{
add => this._applicationCommandsModuleReady.Register(value);
remove => this._applicationCommandsModuleReady.Unregister(value);
}
private AsyncEvent<ApplicationCommandsExtension, ApplicationCommandsModuleReadyEventArgs> _applicationCommandsModuleReady;
/// <summary>
/// Fired when the application commands modules startup is finished.
/// </summary>
public event AsyncEventHandler<ApplicationCommandsExtension, ApplicationCommandsModuleStartupFinishedEventArgs> ApplicationCommandsModuleStartupFinished
{
add => this._applicationCommandsModuleStartupFinished.Register(value);
remove => this._applicationCommandsModuleStartupFinished.Unregister(value);
}
private AsyncEvent<ApplicationCommandsExtension, ApplicationCommandsModuleStartupFinishedEventArgs> _applicationCommandsModuleStartupFinished;
/// <summary>
/// Fired when guild commands are registered on a guild.
/// </summary>
public event AsyncEventHandler<ApplicationCommandsExtension, GuildApplicationCommandsRegisteredEventArgs> GuildApplicationCommandsRegistered
{
add => this._guildApplicationCommandsRegistered.Register(value);
remove => this._guildApplicationCommandsRegistered.Unregister(value);
}
private AsyncEvent<ApplicationCommandsExtension, GuildApplicationCommandsRegisteredEventArgs> _guildApplicationCommandsRegistered;
/// <summary>
/// Fired when the global commands are registered.
/// </summary>
public event AsyncEventHandler<ApplicationCommandsExtension, GlobalApplicationCommandsRegisteredEventArgs> GlobalApplicationCommandsRegistered
{
add => this._globalApplicationCommandsRegistered.Register(value);
remove => this._globalApplicationCommandsRegistered.Unregister(value);
}
private AsyncEvent<ApplicationCommandsExtension, GlobalApplicationCommandsRegisteredEventArgs> _globalApplicationCommandsRegistered;
/// <summary>
/// Used for RegisterCommands and the <see cref="DisCatSharp.DiscordClient.Ready"/> event.
/// </summary>
internal async Task UpdateAsync()
{
this.Client.Logger.Log(ApplicationCommandsLogLevel, "Request to register commands on shard {shard}", this.Client.ShardId);
if (this.StartupFinished)
{
this.Client.Logger.Log(ApplicationCommandsLogLevel, "Shard {shard} already setup, skipping", this.Client.ShardId);
this.FinishedRegistration();
return;
}
GlobalDiscordCommands = new();
GuildDiscordCommands = new();
this.Client.Logger.Log(ApplicationCommandsLogLevel, "Expected Count: {count}", s_expectedCount);
this.Client.Logger.Log(ApplicationCommandsLogLevel, "Shard {shard} has {guilds} guilds.", this.Client.ShardId, this.Client.Guilds?.Count);
List<ulong> failedGuilds = new();
List<DiscordApplicationCommand> globalCommands = null;
globalCommands = (await this.Client.GetGlobalApplicationCommandsAsync(Configuration?.EnableLocalization ?? false)).ToList() ?? null;
var updateList = this._updateList;
var guilds = CheckAllGuilds ? this.Client.Guilds?.Keys.ToList() : updateList.Where(x => x.Key != null)?.Select(x => x.Key.Value).Distinct().ToList();
var wrongShards = guilds.Where(x => !this.Client.Guilds.ContainsKey(x)).ToList();
if (wrongShards.Any())
{
this.Client.Logger.Log(ApplicationCommandsLogLevel, "Some guilds are not on the same shard as the client. Removing them from the update list.");
foreach (var guild in wrongShards)
{
updateList.RemoveAll(x => x.Key == guild);
guilds.Remove(guild);
}
}
var commandsPending = updateList.Select(x => x.Key).Distinct().ToList();
s_expectedCount = commandsPending.Count;
foreach (var guild in guilds)
{
List<DiscordApplicationCommand> commands = null;
var unauthorized = false;
try
{
commands = (await this.Client.GetGuildApplicationCommandsAsync(guild, Configuration?.EnableLocalization ?? false)).ToList() ?? null;
}
catch (UnauthorizedException)
{
unauthorized = true;
}
finally
{
if (!unauthorized && commands != null && commands.Any())
GuildDiscordCommands.Add(guild, commands.ToList());
else if (unauthorized)
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))
{
updateList.Add(new KeyValuePair<ulong?, ApplicationCommandsModuleConfiguration>
(null, new ApplicationCommandsModuleConfiguration(typeof(DefaultHelpModule))));
commandsPending = updateList.Select(x => x.Key).Distinct().ToList();
}
if (globalCommands != null && globalCommands.Any())
GlobalDiscordCommands.AddRange(globalCommands);
foreach (var key in commandsPending)
{
this.Client.Logger.Log(ApplicationCommandsLogLevel, key.HasValue ? $"Registering commands in guild {key.Value}" : "Registering global commands.");
if (key.HasValue)
{
this.Client.Logger.Log(ApplicationCommandsLogLevel, "Found guild {guild} in shard {shard}!", key.Value, this.Client.ShardId);
this.Client.Logger.Log(ApplicationCommandsLogLevel, "Registering");
}
await this.RegisterCommands(updateList.Where(x => x.Key == key).Select(x => x.Value).ToList(), key);
}
this._missingScopeGuildIds = new(failedGuilds);
await this._applicationCommandsModuleReady.InvokeAsync(this, new ApplicationCommandsModuleReadyEventArgs(Configuration?.ServiceProvider)
{
GuildsWithoutScope = failedGuilds
});
this.Client.GuildDownloadCompleted -= async (c, e) => await this.UpdateAsync();
}
/// <summary>
/// Method for registering commands for a target from modules.
/// </summary>
/// <param name="types">The types.</param>
/// <param name="guildId">The optional guild id.</param>
private async Task RegisterCommands(List<ApplicationCommandsModuleConfiguration> types, ulong? guildId)
{
this.Client.Logger.Log(ApplicationCommandsLogLevel, "Registering commands on shard {shard}", this.Client.ShardId);
//Initialize empty lists to be added to the global ones at the end
var commandMethods = new List<CommandMethod>();
var groupCommands = new List<GroupCommand>();
var subGroupCommands = new List<SubGroupCommand>();
var contextMenuCommands = new List<ContextMenuCommand>();
var updateList = new List<DiscordApplicationCommand>();
var commandTypeSources = new List<KeyValuePair<Type, Type>>();
var groupTranslation = new List<GroupTranslator>();
var translation = new List<CommandTranslator>();
//Iterates over all the modules
foreach (var config in types)
{
var type = config.Type;
try
{
var module = type.GetTypeInfo();
var classes = new List<TypeInfo>();
var ctx = new ApplicationCommandsTranslationContext(type, module.FullName);
config.Translations?.Invoke(ctx);
//Add module to classes list if it's a group
var extremeNestedGroup = false;
if (module.GetCustomAttribute<SlashCommandGroupAttribute>() != null)
{
classes.Add(module);
}
else if (module.GetMembers(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static).Any(x => x.IsDefined(typeof(SlashCommandGroupAttribute))))
{
//Otherwise add the extreme nested groups
classes = module.GetMembers(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)
.Where(x => x.IsDefined(typeof(SlashCommandGroupAttribute)))
.Select(x => module.GetNestedType(x.Name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static).GetTypeInfo()).ToList();
extremeNestedGroup = true;
}
else
{
//Otherwise add the nested groups
classes = module.DeclaredNestedTypes.Where(x => x.GetCustomAttribute<SlashCommandGroupAttribute>() != null).ToList();
}
if (module.GetCustomAttribute<SlashCommandGroupAttribute>() != null || extremeNestedGroup)
{
List<GroupTranslator> groupTranslations = null;
if (!string.IsNullOrEmpty(ctx.GroupTranslations))
{
groupTranslations = JsonConvert.DeserializeObject<List<GroupTranslator>>(ctx.GroupTranslations);
}
var slashGroupsTuple = await NestedCommandWorker.ParseSlashGroupsAsync(type, classes, guildId, groupTranslations);
if (slashGroupsTuple.applicationCommands != null && slashGroupsTuple.applicationCommands.Any())
{
updateList.AddRange(slashGroupsTuple.applicationCommands);
if (Configuration.GenerateTranslationFilesOnly)
{
var cgwsgs = new List<CommandGroupWithSubGroups>();
var cgs2 = new List<CommandGroup>();
foreach (var cmd in slashGroupsTuple.applicationCommands)
{
if (cmd.Type == ApplicationCommandType.ChatInput)
{
if (cmd.Options.First().Type == ApplicationCommandOptionType.SubCommandGroup)
{
var cgs = new List<CommandGroup>();
foreach (var scg in cmd.Options)
{
var cs = new List<Command>();
foreach (var sc in scg.Options)
{
if (sc.Options == null || !sc.Options.Any())
cs.Add(new Command(sc.Name, sc.Description, null, null));
else
cs.Add(new Command(sc.Name, sc.Description, sc.Options.ToList(), null));
}
cgs.Add(new CommandGroup(scg.Name, scg.Description, cs, null));
}
cgwsgs.Add(new CommandGroupWithSubGroups(cmd.Name, cmd.Description, cgs, ApplicationCommandType.ChatInput));
}
else if (cmd.Options.First().Type == ApplicationCommandOptionType.SubCommand)
{
var cs2 = new List<Command>();
foreach (var sc2 in cmd.Options)
{
if (sc2.Options == null || !sc2.Options.Any())
cs2.Add(new Command(sc2.Name, sc2.Description, null, null));
else
cs2.Add(new Command(sc2.Name, sc2.Description, sc2.Options.ToList(), null));
}
cgs2.Add(new CommandGroup(cmd.Name, cmd.Description, cs2, ApplicationCommandType.ChatInput));
}
}
}
if (cgwsgs.Any())
foreach (var cgwsg in cgwsgs)
groupTranslation.Add(JsonConvert.DeserializeObject<GroupTranslator>(JsonConvert.SerializeObject(cgwsg)));
if (cgs2.Any())
foreach (var cg2 in cgs2)
groupTranslation.Add(JsonConvert.DeserializeObject<GroupTranslator>(JsonConvert.SerializeObject(cg2)));
}
}
if (slashGroupsTuple.commandTypeSources != null && slashGroupsTuple.commandTypeSources.Any())
commandTypeSources.AddRange(slashGroupsTuple.commandTypeSources);
if (slashGroupsTuple.singletonModules != null && slashGroupsTuple.singletonModules.Any())
s_singletonModules.AddRange(slashGroupsTuple.singletonModules);
if (slashGroupsTuple.groupCommands != null && slashGroupsTuple.groupCommands.Any())
groupCommands.AddRange(slashGroupsTuple.groupCommands);
if (slashGroupsTuple.subGroupCommands != null && slashGroupsTuple.subGroupCommands.Any())
subGroupCommands.AddRange(slashGroupsTuple.subGroupCommands);
}
//Handles methods and context menus, only if the module isn't a group itself
if (module.GetCustomAttribute<SlashCommandGroupAttribute>() == null)
{
List<CommandTranslator> commandTranslations = null;
if (!string.IsNullOrEmpty(ctx.SingleTranslations))
{
commandTranslations = JsonConvert.DeserializeObject<List<CommandTranslator>>(ctx.SingleTranslations);
}
//Slash commands
var methods = module.DeclaredMethods.Where(x => x.GetCustomAttribute<SlashCommandAttribute>() != null);
var slashCommands = await CommandWorker.ParseBasicSlashCommandsAsync(type, methods, guildId, commandTranslations);
if (slashCommands.applicationCommands != null && slashCommands.applicationCommands.Any())
{
updateList.AddRange(slashCommands.applicationCommands);
if (Configuration.GenerateTranslationFilesOnly)
{
var cs = new List<Command>();
foreach (var cmd in slashCommands.applicationCommands)
if (cmd.Type == ApplicationCommandType.ChatInput && (cmd.Options == null || !cmd.Options.Any(x => x.Type == ApplicationCommandOptionType.SubCommand || x.Type == ApplicationCommandOptionType.SubCommandGroup)))
{
if (cmd.Options == null || !cmd.Options.Any())
cs.Add(new Command(cmd.Name, cmd.Description, null, ApplicationCommandType.ChatInput));
else
cs.Add(new Command(cmd.Name, cmd.Description, cmd.Options.ToList(), ApplicationCommandType.ChatInput));
}
if (cs.Any())
foreach (var c in cs)
translation.Add(JsonConvert.DeserializeObject<CommandTranslator>(JsonConvert.SerializeObject(c)));
}
}
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<ContextMenuAttribute>() != null);
var contextCommands = await CommandWorker.ParseContextMenuCommands(type, contextMethods, commandTranslations);
if (contextCommands.applicationCommands != null && contextCommands.applicationCommands.Any())
{
updateList.AddRange(contextCommands.applicationCommands);
if (Configuration.GenerateTranslationFilesOnly)
{
var cs = new List<Command>();
foreach (var cmd in contextCommands.applicationCommands)
if (cmd.Type == ApplicationCommandType.Message || cmd.Type == ApplicationCommandType.User)
cs.Add(new Command(cmd.Name, null, null, cmd.Type));
if (cs.Any())
foreach (var c in cs)
translation.Add(JsonConvert.DeserializeObject<CommandTranslator>(JsonConvert.SerializeObject(c)));
}
}
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<ApplicationCommandModuleLifespanAttribute>() != null && module.GetCustomAttribute<ApplicationCommandModuleLifespanAttribute>().Lifespan == ApplicationCommandModuleLifespan.Singleton)
s_singletonModules.Add(CreateInstance(module, Configuration?.ServiceProvider));
}
}
catch (NullReferenceException ex)
{
this.Client.Logger.LogCritical(ex, "NRE Exception thrown: {msg}\nStack: {stack}", ex.Message, ex.StackTrace);
}
catch (Exception ex)
{
if (ex is BadRequestException brex)
{
this.Client.Logger.LogCritical(brex, @"There was an error registering application commands: {res}", brex.WebResponse.Response);
}
else
{
if (ex.InnerException is not null && ex.InnerException is BadRequestException brex1)
this.Client.Logger.LogCritical(brex1, @"There was an error registering application commands: {res}", brex1.WebResponse.Response);
else
this.Client.Logger.LogCritical(ex, @"There was an error parsing the application commands");
}
s_errored = true;
}
}
if (!s_errored)
{
updateList = updateList.DistinctBy(x => x.Name).ToList();
if (Configuration.GenerateTranslationFilesOnly)
{
s_registrationCount++;
this.CheckRegistrationStartup(ManOr, translation, groupTranslation);
}
else
{
try
{
List<DiscordApplicationCommand> commands = new();
try
{
if (guildId == null)
{
if (updateList != null && updateList.Any())
{
var regCommands = await RegistrationWorker.RegisterGlobalCommandsAsync(this.Client, updateList);
var actualCommands = regCommands.Distinct().ToList();
commands.AddRange(actualCommands);
GlobalCommandsInternal.AddRange(actualCommands);
}
else
{
foreach (var cmd in GlobalDiscordCommands)
{
try
{
await this.Client.DeleteGlobalApplicationCommandAsync(cmd.Id);
}
catch (NotFoundException)
{
this.Client.Logger.Log(ApplicationCommandsLogLevel, "Could not delete global command {cmdId}. Please clean up manually", cmd.Id);
}
}
}
}
else
{
if (updateList != null && updateList.Any())
{
var regCommands = await RegistrationWorker.RegisterGuildCommandsAsync(this.Client, guildId.Value, updateList);
var actualCommands = regCommands.Distinct().ToList();
commands.AddRange(actualCommands);
GuildCommandsInternal.Add(guildId.Value, actualCommands);
try
{
if (this.Client.Guilds.TryGetValue(guildId.Value, out var guild))
guild.InternalRegisteredApplicationCommands.AddRange(actualCommands);
}
catch (NullReferenceException)
{ }
}
else
{
foreach (var cmd in GuildDiscordCommands.First(x => x.Key == guildId.Value).Value)
{
try
{
await this.Client.DeleteGuildApplicationCommandAsync(guildId.Value, cmd.Id);
}
catch (NotFoundException)
{
this.Client.Logger.Log(ApplicationCommandsLogLevel, "Could not delete guild command {cmdId} in guild {guildId}. Please clean up manually", cmd.Id, guildId.Value);
}
}
}
}
}
catch (UnauthorizedException ex)
{
this.Client.Logger.LogError("Could not register application commands for guild {guildId}.\nError: {exc}", guildId, 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)
{
if (commandMethods.GetFirstValueWhere(x => x.Name == command.Name, out var com))
com.CommandId = command.Id;
else if (groupCommands.GetFirstValueWhere(x => x.Name == command.Name, out var groupCom))
groupCom.CommandId = command.Id;
else if (subGroupCommands.GetFirstValueWhere(x => x.Name == command.Name, out var subCom))
subCom.CommandId = command.Id;
else if (contextMenuCommands.GetFirstValueWhere(x => x.Name == command.Name, out var cmCom))
cmCom.CommandId = command.Id;
}
//Adds to the global lists finally
s_commandMethods.AddRange(commandMethods.DistinctBy(x => x.Name));
s_groupCommands.AddRange(groupCommands.DistinctBy(x => x.Name));
s_subGroupCommands.AddRange(subGroupCommands.DistinctBy(x => x.Name));
s_contextMenuCommands.AddRange(contextMenuCommands.DistinctBy(x => x.Name));
s_registeredCommands.Add(new KeyValuePair<ulong?, IReadOnlyList<DiscordApplicationCommand>>(guildId, commands.ToList()));
foreach (var command in commandMethods)
{
var app = types.First(t => t.Type == command.Method.DeclaringType);
}
this.Client.Logger.Log(ApplicationCommandsLogLevel, "Expected Count: {exp}\nCurrent Count: {cur}", s_expectedCount, s_registrationCount);
if (guildId.HasValue)
{
await this._guildApplicationCommandsRegistered.InvokeAsync(this, new GuildApplicationCommandsRegisteredEventArgs(Configuration?.ServiceProvider)
{
Handled = true,
GuildId = guildId.Value,
RegisteredCommands = GuildCommandsInternal.Any(c => c.Key == guildId.Value) ? GuildCommandsInternal.FirstOrDefault(c => c.Key == guildId.Value).Value : null
});
}
else
{
await this._globalApplicationCommandsRegistered.InvokeAsync(this, new GlobalApplicationCommandsRegisteredEventArgs(Configuration?.ServiceProvider)
{
Handled = true,
RegisteredCommands = GlobalCommandsInternal
});
}
s_registrationCount++;
this.CheckRegistrationStartup(ManOr);
}
catch (NullReferenceException ex)
{
this.Client.Logger.LogCritical(ex, "NRE Exception thrown: {msg}\nStack: {stack}", ex.Message, ex.StackTrace);
}
catch (Exception ex)
{
if (ex is BadRequestException brex)
{
this.Client.Logger.LogCritical(brex, @"There was an error registering application commands: {res}", brex.WebResponse.Response);
}
else
{
if (ex.InnerException is not null && ex.InnerException is BadRequestException brex1)
this.Client.Logger.LogCritical(brex1, @"There was an error registering application commands: {res}", brex1.WebResponse.Response);
else
this.Client.Logger.LogCritical(ex, @"There was an general error registering application commands");
}
s_errored = true;
}
}
}
}
private async void CheckRegistrationStartup(bool man = false, List<CommandTranslator> translation = null, List<GroupTranslator> groupTranslation = null)
{
this.Client.Logger.Log(ApplicationCommandsLogLevel, "Checking counts...\n\nExpected Count: {exp}\nCurrent Count: {cur}", s_expectedCount, s_registrationCount);
if ((s_registrationCount == s_expectedCount) || man)
{
await this._applicationCommandsModuleStartupFinished.InvokeAsync(this, new ApplicationCommandsModuleStartupFinishedEventArgs(Configuration?.ServiceProvider)
{
Handled = true,
RegisteredGlobalCommands = GlobalCommandsInternal,
RegisteredGuildCommands = GuildCommandsInternal,
GuildsWithoutScope = this._missingScopeGuildIds
});
if (Configuration.GenerateTranslationFilesOnly)
{
try
{
if (translation != null && translation.Any())
{
var file_name = $"translation_generator_export-shard{this.Client.ShardId}-SINGLE-{s_registrationCount}_of_{s_expectedCount}.json";
var fs = File.Create(file_name);
var ms = new MemoryStream();
var writer = new StreamWriter(ms);
await writer.WriteAsync(JsonConvert.SerializeObject(translation.DistinctBy(x => x.Name), Formatting.Indented));
await writer.FlushAsync();
ms.Position = 0;
await ms.CopyToAsync(fs);
await fs.FlushAsync();
fs.Close();
await fs.DisposeAsync();
ms.Close();
await ms.DisposeAsync();
this.Client.Logger.LogInformation("Exported base translation to {exppath}", file_name);
}
if (groupTranslation != null && groupTranslation.Any())
{
var file_name = $"translation_generator_export-shard{this.Client.ShardId}-GROUP-{s_registrationCount}_of_{s_expectedCount}.json";
var fs = File.Create(file_name);
var ms = new MemoryStream();
var writer = new StreamWriter(ms);
await writer.WriteAsync(JsonConvert.SerializeObject(groupTranslation.DistinctBy(x => x.Name), Formatting.Indented));
await writer.FlushAsync();
ms.Position = 0;
await ms.CopyToAsync(fs);
await fs.FlushAsync();
fs.Close();
await fs.DisposeAsync();
ms.Close();
await ms.DisposeAsync();
this.Client.Logger.LogInformation("Exported base translation to {exppath}", file_name);
}
}
catch (Exception ex)
{
this.Client.Logger.LogError(@"{msg}", ex.Message);
this.Client.Logger.LogError(@"{stack}", ex.StackTrace);
}
this.FinishedRegistration();
await this.Client.DisconnectAsync();
}
else
{
this.FinishedRegistration();
}
}
}
/// <summary>
/// Interaction handler.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task InteractionHandler(DiscordClient client, InteractionCreateEventArgs e)
{
this.Client.Logger.Log(ApplicationCommandsLogLevel, "Got slash interaction on shard {shard}", this.Client.ShardId);
if (HandledInteractions.Contains(e.Interaction.Id))
{
this.Client.Logger.Log(ApplicationCommandsLogLevel, "Ignoring, already received");
return Task.FromResult(true);
}
else
HandledInteractions.Add(e.Interaction.Id);
_ = 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,
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,
AppPermissions = e.Interaction.AppPermissions
};
try
{
if (s_errored)
{
await e.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().WithContent("Application commands failed to register properly on startup."));
throw new InvalidOperationException("Application commands failed to register properly on startup.");
}
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())
{
await e.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().WithContent("A application command was executed, but no command was registered for it."));
throw new InvalidOperationException("A application command was executed, but no command was registered for it.");
}
if (methods.Any())
{
var method = methods.First().Method;
this.Client.Logger.LogDebug("Executing {cmd}", method.Name);
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[0];
var method = groups.First().Methods.First(x => x.Key == command.Name).Value;
this.Client.Logger.LogDebug("Executing {cmd}", method.Name);
var args = await this.ResolveInteractionCommandParameters(e, context, method, e.Interaction.Data.Options[0].Options);
await this.RunCommandAsync(context, method, args);
}
else if (subgroups.Any())
{
var command = e.Interaction.Data.Options[0];
var group = subgroups.First().SubCommands.First(x => x.Name == command.Name);
var method = group.Methods.First(x => x.Key == command.Options[0].Name).Value;
this.Client.Logger.LogDebug("Executing {cmd}", method.Name);
var args = await this.ResolveInteractionCommandParameters(e, context, method, e.Interaction.Data.Options[0].Options[0].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 (s_errored)
{
await e.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().WithContent("Application commands failed to register properly on startup."));
throw new InvalidOperationException("Application commands failed to register properly on startup.");
}
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())
{
await e.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().WithContent("An autocomplete interaction was created, but no command was registered for it"));
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<OptionAttribute>().Name == focusedOption.Name);
var provider = option.GetCustomAttribute<AutocompleteAttribute>().ProviderType;
var providerMethod = provider.GetMethod(nameof(IAutocompleteProvider.Provider));
var providerInstance = Activator.CreateInstance(provider);
var context = new AutocompleteContext
{
Interaction = e.Interaction,
Client = client,
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,
AppPermissions = e.Interaction.AppPermissions
};
var choices = await (Task<IEnumerable<DiscordApplicationCommandAutocompleteChoice>>) 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[0];
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<OptionAttribute>().Name == focusedOption.Name);
var provider = option.GetCustomAttribute<AutocompleteAttribute>().ProviderType;
var providerMethod = provider.GetMethod(nameof(IAutocompleteProvider.Provider));
var providerInstance = Activator.CreateInstance(provider);
var context = new AutocompleteContext
{
Client = client,
Interaction = e.Interaction,
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,
AppPermissions = e.Interaction.AppPermissions
};
var choices = await (Task<IEnumerable<DiscordApplicationCommandAutocompleteChoice>>) 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[0];
var group = subgroups.First().SubCommands.First(x => x.Name == command.Name).Methods.First(x => x.Key == command.Options[0].Name).Value;
var focusedOption = command.Options[0].Options.First(o => o.Focused);
var option = group.GetParameters().Skip(1).First(p => p.GetCustomAttribute<OptionAttribute>().Name == focusedOption.Name);
var provider = option.GetCustomAttribute<AutocompleteAttribute>().ProviderType;
var providerMethod = provider.GetMethod(nameof(IAutocompleteProvider.Provider));
var providerInstance = Activator.CreateInstance(provider);
var context = new AutocompleteContext
{
Client = client,
Interaction = e.Interaction,
Services = Configuration?.ServiceProvider,
ApplicationCommandsExtension = this,
Guild = e.Interaction.Guild,
Channel = e.Interaction.Channel,
User = e.Interaction.User,
Options = command.Options[0].Options.ToList(),
FocusedOption = focusedOption,
Locale = e.Interaction.Locale,
GuildLocale = e.Interaction.GuildLocale,
AppPermissions = e.Interaction.AppPermissions
};
var choices = await (Task<IEnumerable<DiscordApplicationCommandAutocompleteChoice>>) 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;
}
/// <summary>
/// Context menu handler.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task ContextMenuHandler(DiscordClient client, ContextMenuInteractionCreateEventArgs e)
{
this.Client.Logger.Log(ApplicationCommandsLogLevel, "Got context menu interaction on shard {shard}", this.Client.ShardId);
if (HandledInteractions.Contains(e.Interaction.Id))
{
this.Client.Logger.Log(ApplicationCommandsLogLevel, "Ignoring, already received");
return Task.FromResult(true);
}
else
HandledInteractions.Add(e.Interaction.Id);
_ = Task.Run(async () =>
{
//Creates the context
var context = new ContextMenuContext
{
Interaction = e.Interaction,
Channel = e.Interaction.Channel,
Client = client,
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,
AppPermissions = e.Interaction.AppPermissions
};
try
{
if (s_errored)
{
await e.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().WithContent("Context menus failed to register properly on startup."));
throw new InvalidOperationException("Context menus failed to register properly on startup.");
}
//Gets the method for the command
var method = s_contextMenuCommands.FirstOrDefault(x => x.CommandId == e.Interaction.Data.Id);
if (method == null)
{
await e.Interaction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder().WithContent("A context menu command was executed, but no command was registered for it."));
throw new InvalidOperationException("A context menu command 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;
}
/// <summary>
/// Runs a command.
/// </summary>
/// <param name="context">The base context.</param>
/// <param name="method">The method info.</param>
/// <param name="args">The arguments.</param>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0066:Convert switch statement to expression", Justification = "<Pending>")]
internal async Task RunCommandAsync(BaseContext context, MethodInfo method, IEnumerable<object> args)
{
object classInstance;
this.Client.Logger.Log(ApplicationCommandsLogLevel, "Executing {cmd}", method.Name);
//Accounts for lifespans
var moduleLifespan = (method.DeclaringType.GetCustomAttribute<ApplicationCommandModuleLifespanAttribute>() != null ? method.DeclaringType.GetCustomAttribute<ApplicationCommandModuleLifespanAttribute>()?.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);
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);
break;
//If singleton, gets it from the singleton list
case ApplicationCommandModuleLifespan.Singleton:
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)
{
if (AutoDeferEnabled)
await context.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource);
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)
{
if (AutoDeferEnabled)
await context.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource);
await (Task)method.Invoke(classInstance, args.ToArray());
await (module?.AfterContextMenuExecutionAsync(contextMenuContext) ?? Task.CompletedTask);
}
}
}
/// <summary>
/// Property injection
/// </summary>
/// <param name="t">The type.</param>
/// <param name="services">The services.</param>
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<DontInjectAttribute>() != 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<DontInjectAttribute>() != null)
continue;
var service = services.GetService(field.FieldType);
if (service == null)
continue;
field.SetValue(moduleInstance, service);
}
return moduleInstance;
}
/// <summary>
/// Resolves the slash command parameters.
/// </summary>
/// <param name="e">The event arguments.</param>
/// <param name="context">The interaction context.</param>
/// <param name="method">The method info.</param>
/// <param name="options">The options.</param>
private async Task<List<object>> ResolveInteractionCommandParameters(InteractionCreateEventArgs e, InteractionContext context, MethodInfo method, IEnumerable<DiscordInteractionDataOption> options)
{
var args = new List<object> { context };
var parameters = method.GetParameters().Skip(1);
foreach (var parameter in parameters)
{
//Accounts for optional arguments without values given
if (parameter.IsOptional && (options == null || (!options?.Any(x => x.Name == parameter.GetCustomAttribute<OptionAttribute>().Name.ToLower()) ?? true)))
args.Add(parameter.DefaultValue);
else
{
var option = options.Single(x => x.Name == parameter.GetCustomAttribute<OptionAttribute>().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(ulong) || parameter.ParameterType == typeof(ulong?))
args.Add((ulong?)option.Value);
else if (parameter.ParameterType == typeof(int) || parameter.ParameterType == typeof(int?))
args.Add((int?)option.Value);
else if (parameter.ParameterType == typeof(long) || parameter.ParameterType == typeof(long?))
if (option.Value == null)
args.Add(null);
else
args.Add(Convert.ToInt64(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.Channels != null && e.Interaction.Data.Resolved.Channels.TryGetValue((ulong)option.Value, out var channel))
args.Add(channel);
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
throw new ArgumentException("Error resolving mentionable option.");
}
else
throw new ArgumentException($"Error resolving interaction.");
}
}
return args;
}
/// <summary>
/// Runs the pre-execution checks.
/// </summary>
/// <param name="method">The method info.</param>
/// <param name="context">The base context.</param>
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<ApplicationCommandCheckBaseAttribute>();
attributes.AddRange(method.GetCustomAttributes<ApplicationCommandCheckBaseAttribute>(true));
attributes.AddRange(method.DeclaringType.GetCustomAttributes<ApplicationCommandCheckBaseAttribute>());
if (method.DeclaringType.DeclaringType != null)
{
attributes.AddRange(method.DeclaringType.DeclaringType.GetCustomAttributes<ApplicationCommandCheckBaseAttribute>());
if (method.DeclaringType.DeclaringType.DeclaringType != null)
{
attributes.AddRange(method.DeclaringType.DeclaringType.DeclaringType.GetCustomAttributes<ApplicationCommandCheckBaseAttribute>());
}
}
var dict = new Dictionary<ApplicationCommandCheckBaseAttribute, bool>();
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)
{
var attributes = new List<ApplicationCommandCheckBaseAttribute>();
attributes.AddRange(method.GetCustomAttributes<ApplicationCommandCheckBaseAttribute>(true));
attributes.AddRange(method.DeclaringType.GetCustomAttributes<ApplicationCommandCheckBaseAttribute>());
if (method.DeclaringType.DeclaringType != null)
{
attributes.AddRange(method.DeclaringType.DeclaringType.GetCustomAttributes<ApplicationCommandCheckBaseAttribute>());
if (method.DeclaringType.DeclaringType.DeclaringType != null)
{
attributes.AddRange(method.DeclaringType.DeclaringType.DeclaringType.GetCustomAttributes<ApplicationCommandCheckBaseAttribute>());
}
}
var dict = new Dictionary<ApplicationCommandCheckBaseAttribute, bool>();
foreach (var att in attributes)
{
//Runs the check and adds the result to a list
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() };
}
}
/// <summary>
/// Gets the choice attributes from choice provider.
/// </summary>
/// <param name="customAttributes">The custom attributes.</param>
/// <param name="guildId">The optional guild id</param>
private static async Task<List<DiscordApplicationCommandOptionChoice>> GetChoiceAttributesFromProvider(IEnumerable<ChoiceProviderAttribute> customAttributes, ulong? guildId = null)
{
var choices = new List<DiscordApplicationCommandOptionChoice>();
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);
}
//Gets the choices from the method
var result = await (Task<IEnumerable<DiscordApplicationCommandOptionChoice>>)method.Invoke(instance, null);
if (result.Any())
{
choices.AddRange(result);
}
}
}
return choices;
}
/// <summary>
/// Gets the choice attributes from enum parameter.
/// </summary>
/// <param name="enumParam">The enum parameter.</param>
private static List<DiscordApplicationCommandOptionChoice> GetChoiceAttributesFromEnumParameter(Type enumParam)
{
var choices = new List<DiscordApplicationCommandOptionChoice>();
foreach (Enum enumValue in Enum.GetValues(enumParam))
{
choices.Add(new DiscordApplicationCommandOptionChoice(enumValue.GetName(), enumValue.ToString()));
}
return choices;
}
/// <summary>
/// Gets the parameter type.
/// </summary>
/// <param name="type">The type.</param>
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(DiscordAttachment)
? ApplicationCommandOptionType.Attachment
: type == typeof(DiscordChannel)
? ApplicationCommandOptionType.Channel
: type == typeof(DiscordUser)
? ApplicationCommandOptionType.User
: type == typeof(DiscordRole)
? ApplicationCommandOptionType.Role
: type == typeof(SnowflakeObject)
? ApplicationCommandOptionType.Mentionable
: 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;
}
/// <summary>
/// Gets the choice attributes from parameter.
/// </summary>
/// <param name="choiceAttributes">The choice attributes.</param>
private static List<DiscordApplicationCommandOptionChoice> GetChoiceAttributesFromParameter(IEnumerable<ChoiceAttribute> choiceAttributes) =>
!choiceAttributes.Any()
? null
: choiceAttributes.Select(att => new DiscordApplicationCommandOptionChoice(att.Name, att.Value)).ToList();
/// <summary>
/// Parses the parameters.
/// </summary>
/// <param name="parameters">The parameters.</param>
/// <param name="commandName">The command name.</param>
/// <param name="guildId">The optional guild id.</param>
internal static async Task<List<DiscordApplicationCommandOption>> ParseParametersAsync(IEnumerable<ParameterInfo> parameters, string commandName, ulong? guildId)
{
var options = new List<DiscordApplicationCommandOption>();
foreach (var parameter in parameters)
{
//Gets the attribute
var optionAttribute = parameter.GetCustomAttribute<OptionAttribute>();
if (optionAttribute == null)
throw new ArgumentException($"One or more arguments of the command '{commandName}' are missing the Option attribute!");
var minimumValue = parameter.GetCustomAttribute<MinimumValueAttribute>()?.Value ?? null;
var maximumValue = parameter.GetCustomAttribute<MaximumValueAttribute>()?.Value ?? null;
var minimumLength = parameter.GetCustomAttribute<MinimumLengthAttribute>()?.Value ?? null;
var maximumLength = parameter.GetCustomAttribute<MaximumLengthAttribute>()?.Value ?? null;
var channelTypes = parameter.GetCustomAttribute<ChannelTypesAttribute>()?.ChannelTypes ?? null;
var autocompleteAttribute = parameter.GetCustomAttribute<AutocompleteAttribute>();
if (optionAttribute.Autocomplete && autocompleteAttribute == null)
throw new ArgumentException($"The command '{commandName}' has autocomplete enabled but is missing an autocomplete attribute!");
if (!optionAttribute.Autocomplete && autocompleteAttribute != null)
throw new ArgumentException($"The command '{commandName}' has an autocomplete provider but the option to have autocomplete set to false!");
//Sets the type
var type = parameter.ParameterType;
var parameterType = GetParameterType(type);
if (parameterType == ApplicationCommandOptionType.String)
{
minimumValue = null;
maximumValue = null;
}
else if (parameterType == ApplicationCommandOptionType.Integer || parameterType == ApplicationCommandOptionType.Number)
{
minimumLength = null;
maximumLength = null;
}
if (parameterType != ApplicationCommandOptionType.Channel)
channelTypes = null;
//Handles choices
//From attributes
var choices = GetChoiceAttributesFromParameter(parameter.GetCustomAttributes<ChoiceAttribute>());
//From enums
if (parameter.ParameterType.IsEnum)
{
choices = GetChoiceAttributesFromEnumParameter(parameter.ParameterType);
}
//From choice provider
var choiceProviders = parameter.GetCustomAttributes<ChoiceProviderAttribute>();
if (choiceProviders.Any())
{
choices = await GetChoiceAttributesFromProvider(choiceProviders, guildId);
}
options.Add(new DiscordApplicationCommandOption(optionAttribute.Name, optionAttribute.Description, parameterType, !parameter.IsOptional, choices, null, channelTypes, optionAttribute.Autocomplete, minimumValue, maximumValue, minimumLength: minimumLength, maximumLength: maximumLength));
}
return options;
}
/*
/// <summary>
/// <para>Refreshes your commands, used for refreshing choice providers or applying commands registered after the ready event on the discord client.</para>
/// <para>Not recommended and should be avoided since it can make slash commands be unresponsive for a while.</para>
/// </summary>
public async Task RefreshCommandsAsync()
{
s_commandMethods.Clear();
s_groupCommands.Clear();
s_subGroupCommands.Clear();
s_registeredCommands.Clear();
s_contextMenuCommands.Clear();
GlobalDiscordCommands.Clear();
GuildDiscordCommands.Clear();
GuildCommandsInternal.Clear();
GlobalCommandsInternal.Clear();
GlobalDiscordCommands = null;
GuildDiscordCommands = null;
s_errored = false;
/*if (Configuration != null && Configuration.EnableDefaultHelp)
{
this._updateList.RemoveAll(x => x.Value.Type == typeof(DefaultHelpModule));
}*/
/*
await this.UpdateAsync();
}*/
/// <summary>
/// Fires when the execution of a slash command fails.
/// </summary>
public event AsyncEventHandler<ApplicationCommandsExtension, SlashCommandErrorEventArgs> SlashCommandErrored
{
add => this._slashError.Register(value);
remove => this._slashError.Unregister(value);
}
private AsyncEvent<ApplicationCommandsExtension, SlashCommandErrorEventArgs> _slashError;
/// <summary>
/// Fires when the execution of a slash command is successful.
/// </summary>
public event AsyncEventHandler<ApplicationCommandsExtension, SlashCommandExecutedEventArgs> SlashCommandExecuted
{
add => this._slashExecuted.Register(value);
remove => this._slashExecuted.Unregister(value);
}
private AsyncEvent<ApplicationCommandsExtension, SlashCommandExecutedEventArgs> _slashExecuted;
/// <summary>
/// Fires when the execution of a context menu fails.
/// </summary>
public event AsyncEventHandler<ApplicationCommandsExtension, ContextMenuErrorEventArgs> ContextMenuErrored
{
add => this._contextMenuErrored.Register(value);
remove => this._contextMenuErrored.Unregister(value);
}
private AsyncEvent<ApplicationCommandsExtension, ContextMenuErrorEventArgs> _contextMenuErrored;
/// <summary>
/// Fire when the execution of a context menu is successful.
/// </summary>
public event AsyncEventHandler<ApplicationCommandsExtension, ContextMenuExecutedEventArgs> ContextMenuExecuted
{
add => this._contextMenuExecuted.Register(value);
remove => this._contextMenuExecuted.Unregister(value);
}
private AsyncEvent<ApplicationCommandsExtension, ContextMenuExecutedEventArgs> _contextMenuExecuted;
}
/// <summary>
/// Holds configuration data for setting up an application command.
/// </summary>
internal class ApplicationCommandsModuleConfiguration
{
/// <summary>
/// The type of the command module.
/// </summary>
public Type Type { get; }
/// <summary>
/// The translation setup.
/// </summary>
public Action<ApplicationCommandsTranslationContext> Translations { get; }
/// <summary>
/// Creates a new command configuration.
/// </summary>
/// <param name="type">The type of the command module.</param>
/// <param name="translations">The translation setup callback.</param>
public ApplicationCommandsModuleConfiguration(Type type, Action<ApplicationCommandsTranslationContext> translations = null)
{
this.Type = type;
this.Translations = translations;
}
}
/// <summary>
/// Links a command to its original command module.
/// </summary>
internal class ApplicationCommandSourceLink
{
/// <summary>
/// The command.
/// </summary>
public DiscordApplicationCommand ApplicationCommand { get; set; }
/// <summary>
/// The base/root module the command is contained in.
/// </summary>
public Type RootCommandContainerType { get; set; }
/// <summary>
/// The direct group the command is contained in.
/// </summary>
public Type CommandContainerType { get; set; }
}
/// <summary>
/// The command method.
/// </summary>
internal class CommandMethod
{
/// <summary>
/// Gets or sets the command id.
/// </summary>
public ulong CommandId { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets the method.
/// </summary>
public MethodInfo Method { get; set; }
}
/// <summary>
/// The group command.
/// </summary>
internal class GroupCommand
{
/// <summary>
/// Gets or sets the command id.
/// </summary>
public ulong CommandId { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets the methods.
/// </summary>
public List<KeyValuePair<string, MethodInfo>> Methods { get; set; } = null;
}
/// <summary>
/// The sub group command.
/// </summary>
internal class SubGroupCommand
{
/// <summary>
/// Gets or sets the command id.
/// </summary>
public ulong CommandId { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets the sub commands.
/// </summary>
public List<GroupCommand> SubCommands { get; set; } = new();
}
/// <summary>
/// The context menu command.
/// </summary>
internal class ContextMenuCommand
{
/// <summary>
/// Gets or sets the command id.
/// </summary>
public ulong CommandId { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets the method.
/// </summary>
public MethodInfo Method { get; set; }
}
#region Default Help
/// <summary>
/// Represents the default help module.
/// </summary>
internal class DefaultHelpModule : ApplicationCommandsModule
{
public class DefaultHelpAutoCompleteProvider : IAutocompleteProvider
{
public async Task<IEnumerable<DiscordApplicationCommandAutocompleteChoice>> Provider(AutocompleteContext context)
{
var options = new List<DiscordApplicationCommandAutocompleteChoice>();
IEnumerable<DiscordApplicationCommand> slashCommands = null;
var globalCommandsTask = context.Client.GetGlobalApplicationCommandsAsync();
if (context.Guild != null)
{
var guildCommandsTask = context.Client.GetGuildApplicationCommandsAsync(context.Guild.Id);
await Task.WhenAll(globalCommandsTask, guildCommandsTask);
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();
}
else
{
await Task.WhenAll(globalCommandsTask);
slashCommands = globalCommandsTask.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<IEnumerable<DiscordApplicationCommandAutocompleteChoice>> Provider(AutocompleteContext context)
{
var options = new List<DiscordApplicationCommandAutocompleteChoice>();
IEnumerable<DiscordApplicationCommand> slashCommands = null;
var globalCommandsTask = context.Client.GetGlobalApplicationCommandsAsync();
if (context.Guild != null)
{
var guildCommandsTask = context.Client.GetGuildApplicationCommandsAsync(context.Guild.Id);
await Task.WhenAll(globalCommandsTask, guildCommandsTask);
slashCommands = globalCommandsTask.Result.Concat(guildCommandsTask.Result)
.Where(ac => !ac.Name.Equals("help", StringComparison.OrdinalIgnoreCase))
.GroupBy(ac => ac.Name).Select(x => x.First());
}
else
{
await Task.WhenAll(globalCommandsTask);
slashCommands = globalCommandsTask.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<IEnumerable<DiscordApplicationCommandAutocompleteChoice>> Provider(AutocompleteContext context)
{
var options = new List<DiscordApplicationCommandAutocompleteChoice>();
IEnumerable<DiscordApplicationCommand> slashCommands = null;
var globalCommandsTask = context.Client.GetGlobalApplicationCommandsAsync();
if (context.Guild != null)
{
var guildCommandsTask = context.Client.GetGuildApplicationCommandsAsync(context.Guild.Id);
await Task.WhenAll(globalCommandsTask, guildCommandsTask);
slashCommands = globalCommandsTask.Result.Concat(guildCommandsTask.Result)
.Where(ac => !ac.Name.Equals("help", StringComparison.OrdinalIgnoreCase))
.GroupBy(ac => ac.Name).Select(x => x.First());
}
else
{
await Task.WhenAll(globalCommandsTask);
slashCommands = globalCommandsTask.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")]
internal 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)
{
List<DiscordApplicationCommand> applicationCommands = null;
var globalCommandsTask = ctx.Client.GetGlobalApplicationCommandsAsync();
if (ctx.Guild != null)
{
var guildCommandsTask= ctx.Client.GetGuildApplicationCommandsAsync(ctx.Guild.Id);
await Task.WhenAll(globalCommandsTask, guildCommandsTask);
applicationCommands = globalCommandsTask.Result.Concat(guildCommandsTask.Result)
.Where(ac => !ac.Name.Equals("help", StringComparison.OrdinalIgnoreCase))
.GroupBy(ac => ac.Name).Select(x => x.First())
.ToList();
}
else
{
await Task.WhenAll(globalCommandsTask);
applicationCommands = globalCommandsTask.Result
.Where(ac => !ac.Name.Equals("help", StringComparison.OrdinalIgnoreCase))
.GroupBy(ac => ac.Name).Select(x => x.First())
.ToList();
}
if (applicationCommands.Count < 1)
{
if (ApplicationCommandsExtension.Configuration.AutoDefer)
await ctx.EditResponseAsync(new DiscordWebhookBuilder()
.WithContent($"There are no slash commands"));
else
await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder()
.WithContent($"There are no slash commands").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 subCommandParent = commandsWithSubCommands.FirstOrDefault(cm => cm.Name.Equals(commandName,StringComparison.OrdinalIgnoreCase));
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 = $"{subCommandParent.Mention.Replace(subCommandParent.Name, $"{subCommandParent.Name} {cmdParent.Name} {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(option.Description ?? "No description provided.").Append('\n');
sb.Append('\n');
discordEmbed.AddField(new DiscordEmbedField("Arguments", sb.ToString().Trim()));
}
if (ApplicationCommandsExtension.Configuration.AutoDefer)
await ctx.EditResponseAsync(new DiscordWebhookBuilder()
.AddEmbed(discordEmbed));
else
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 = $"{subCommandParent.Mention.Replace(subCommandParent.Name, $"{subCommandParent.Name} {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(option.Description ?? "No description provided.").Append('\n');
sb.Append('\n');
discordEmbed.AddField(new DiscordEmbedField("Arguments", sb.ToString().Trim()));
}
if (ApplicationCommandsExtension.Configuration.AutoDefer)
await ctx.EditResponseAsync(new DiscordWebhookBuilder().AddEmbed(discordEmbed));
else
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)
{
if (ApplicationCommandsExtension.Configuration.AutoDefer)
await ctx.EditResponseAsync(new DiscordWebhookBuilder()
.WithContent($"No command called {commandName} in guild {ctx.Guild.Name}"));
else
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 = $"{command.Mention}: {command.Description ?? "No description provided."}"
}.AddField(new DiscordEmbedField("Command is NSFW", command.IsNsfw.ToString()));
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(option.Description ?? "No description provided.").Append('\n');
sb.Append('\n');
discordEmbed.AddField(new DiscordEmbedField("Arguments", sb.ToString().Trim()));
}
if (ApplicationCommandsExtension.Configuration.AutoDefer)
await ctx.EditResponseAsync(new DiscordWebhookBuilder().AddEmbed(discordEmbed));
else
await ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource,
new DiscordInteractionResponseBuilder().AddEmbed(discordEmbed).AsEphemeral(true));
}
}
}
#endregion
diff --git a/DisCatSharp.ApplicationCommands/ApplicationCommandsModule.cs b/DisCatSharp.ApplicationCommands/ApplicationCommandsModule.cs
index c0da1c009..5ebea3d28 100644
--- a/DisCatSharp.ApplicationCommands/ApplicationCommandsModule.cs
+++ b/DisCatSharp.ApplicationCommands/ApplicationCommandsModule.cs
@@ -1,65 +1,65 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Threading.Tasks;
using DisCatSharp.ApplicationCommands.Context;
namespace DisCatSharp.ApplicationCommands;
/// <summary>
/// Represents a base class for application command modules
/// </summary>
public abstract class ApplicationCommandsModule
{
/// <summary>
/// Called before the execution of a slash command in the module.
/// </summary>
/// <param name="ctx">The context.</param>
/// <returns> Whether or not to execute the slash command.</returns>
public virtual Task<bool> BeforeSlashExecutionAsync(InteractionContext ctx)
=> Task.FromResult(true);
/// <summary>
/// Called after the execution of a slash command in the module.
/// </summary>
/// <param name="ctx">The context.</param>
/// <returns></returns>
public virtual Task AfterSlashExecutionAsync(InteractionContext ctx)
=> Task.CompletedTask;
/// <summary>
/// Called before the execution of a context menu in the module.
/// </summary>
/// <param name="ctx">The context.</param>
/// <returns> Whether or not to execute the slash command. </returns>
public virtual Task<bool> BeforeContextMenuExecutionAsync(ContextMenuContext ctx)
=> Task.FromResult(true);
/// <summary>
/// Called after the execution of a context menu in the module.
/// </summary>
/// <param name="ctx">The context.</param>
/// <returns></returns>
public virtual Task AfterContextMenuExecutionAsync(ContextMenuContext ctx)
=> Task.CompletedTask;
}
diff --git a/DisCatSharp.ApplicationCommands/ApplicationCommandsUtilities.cs b/DisCatSharp.ApplicationCommands/ApplicationCommandsUtilities.cs
index 0e1c3d707..bc06ef728 100644
--- a/DisCatSharp.ApplicationCommands/ApplicationCommandsUtilities.cs
+++ b/DisCatSharp.ApplicationCommands/ApplicationCommandsUtilities.cs
@@ -1,99 +1,99 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using DisCatSharp.ApplicationCommands.Context;
namespace DisCatSharp.ApplicationCommands;
public static class ApplicationCommandsUtilities
{
/// <summary>
/// Whether this module is a candidate type.
/// </summary>
/// <param name="type">The type.</param>
internal static bool IsModuleCandidateType(this Type type)
=> type.GetTypeInfo().IsModuleCandidateType();
/// <summary>
/// Whether this module is a candidate type.
/// </summary>
/// <param name="ti">The type info.</param>
internal static bool IsModuleCandidateType(this TypeInfo ti)
{
// check if compiler-generated
if (ti.GetCustomAttribute<CompilerGeneratedAttribute>(false) != null)
return false;
// check if derives from the required base class
var tmodule = typeof(ApplicationCommandsModule);
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());
}
/// <summary>
/// Whether this is a command candidate.
/// </summary>
/// <param name="method">The method.</param>
/// <param name="parameters">The parameters.</param>
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(BaseContext) || method.ReturnType != typeof(Task))
return false;
// qualifies
return true;
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/ApplicationCommandModuleLifespanAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/ApplicationCommandModuleLifespanAttribute.cs
index 94243b6a6..23ff16716 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/ApplicationCommandModuleLifespanAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/ApplicationCommandModuleLifespanAttribute.cs
@@ -1,48 +1,48 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.ApplicationCommands.Enums;
namespace DisCatSharp.ApplicationCommands.Attributes;
/// <summary>
/// Defines this application command module's lifespan. Module lifespans are transient by default.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class ApplicationCommandModuleLifespanAttribute : Attribute
{
/// <summary>
/// Gets the lifespan.
/// </summary>
public ApplicationCommandModuleLifespan Lifespan { get; }
/// <summary>
/// Defines this application command module's lifespan.
/// </summary>
/// <param name="lifespan">The lifespan of the module. Module lifespans are transient by default.</param>
public ApplicationCommandModuleLifespanAttribute(ApplicationCommandModuleLifespan lifespan)
{
this.Lifespan = lifespan;
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/CheckBaseAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/CheckBaseAttribute.cs
index edc1ef51c..1fba320b5 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/CheckBaseAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/CheckBaseAttribute.cs
@@ -1,41 +1,41 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Threading.Tasks;
using DisCatSharp.ApplicationCommands.Context;
namespace DisCatSharp.ApplicationCommands.Attributes;
/// <summary>
/// The base class for a pre-execution check for a application command.
/// </summary>
public abstract class ApplicationCommandCheckBaseAttribute : Attribute
{
/// <summary>
/// Checks whether this command can be executed within the current context.
/// </summary>
/// <param name="ctx">The context.</param>
/// <returns>Whether the checks passed.</returns>
public abstract Task<bool> ExecuteChecksAsync(BaseContext ctx);
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/ContextMenu/ContextMenuAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/ContextMenu/ContextMenuAttribute.cs
index ab869243e..6ccdbb2d2 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/ContextMenu/ContextMenuAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/ContextMenu/ContextMenuAttribute.cs
@@ -1,97 +1,97 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
namespace DisCatSharp.ApplicationCommands.Attributes;
/// <summary>
/// Marks this method as a context menu.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public sealed class ContextMenuAttribute : Attribute
{
/// <summary>
/// Gets the name of this context menu.
/// </summary>
public string Name { get; internal set; }
/// <summary>
/// Gets the type of this context menu.
/// </summary>
public ApplicationCommandType Type { get; internal set; }
/// <summary>
/// Gets the commands needed permissions.
/// </summary>
public Permissions? DefaultMemberPermissions { get; internal set; }
/// <summary>
/// Gets whether the command can be used in direct messages.
/// </summary>
internal bool? DmPermission { get; set; }
/// <summary>
/// Gets whether this command is marked as NSFW
/// </summary>
public bool IsNsfw { get; set; }
/// <summary>
/// Marks this method as a context menu.
/// </summary>
/// <param name="type">The type of the context menu.</param>
/// <param name="name">The name of the context menu.</param>
/// <param name="isNsfw">Whether this context menu command is marked as NSFW.</param>
public ContextMenuAttribute(ApplicationCommandType type, string name, bool isNsfw = false)
{
if (type == ApplicationCommandType.ChatInput)
throw new ArgumentException("Context menus cannot be of type ChatInput (Slash).");
this.Type = type;
this.Name = name;
this.DefaultMemberPermissions = null;
this.DmPermission = null;
this.IsNsfw = isNsfw;
}
/// <summary>
/// Marks this method as a context menu.
/// </summary>
/// <param name="type">The type of the context menu.</param>
/// <param name="name">The name of the context menu.</param>
/// <param name="defaultMemberPermissions">The default member permissions.</param>
/// <param name="isNsfw">Whether this context menu command is marked as NSFW.</param>
public ContextMenuAttribute(ApplicationCommandType type, string name, long defaultMemberPermissions, bool isNsfw = false)
{
if (type == ApplicationCommandType.ChatInput)
throw new ArgumentException("Context menus cannot be of type ChatInput (Slash).");
this.Type = type;
this.Name = name;
this.DefaultMemberPermissions = (Permissions)defaultMemberPermissions;
this.DmPermission = null;
this.IsNsfw = isNsfw;
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/ContextMenu/ContextMenuCooldownAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/ContextMenu/ContextMenuCooldownAttribute.cs
index 6db69187d..b7757bc4f 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/ContextMenu/ContextMenuCooldownAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/ContextMenu/ContextMenuCooldownAttribute.cs
@@ -1,156 +1,156 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Tasks;
using DisCatSharp.ApplicationCommands.Context;
using DisCatSharp.ApplicationCommands.Entities;
using DisCatSharp.ApplicationCommands.Enums;
namespace DisCatSharp.ApplicationCommands.Attributes;
/// <summary>
/// Defines a cooldown for this command. This allows you to define how many times can users execute a specific command
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public sealed class ContextMenuCooldownAttribute : ApplicationCommandCheckBaseAttribute, ICooldown<BaseContext, ContextMenuCooldownBucket>
{
/// <summary>
/// Gets the maximum number of uses before this command triggers a cooldown for its bucket.
/// </summary>
public int MaxUses { get; }
/// <summary>
/// Gets the time after which the cooldown is reset.
/// </summary>
public TimeSpan Reset { get; }
/// <summary>
/// Gets the type of the cooldown bucket. This determines how cooldowns are applied.
/// </summary>
public CooldownBucketType BucketType { get; }
/// <summary>
/// Gets the cooldown buckets for this command.
/// </summary>
internal readonly ConcurrentDictionary<string, ContextMenuCooldownBucket> _buckets;
/// <summary>
/// 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.
/// </summary>
/// <param name="maxUses">Number of times the command can be used before triggering a cooldown.</param>
/// <param name="resetAfter">Number of seconds after which the cooldown is reset.</param>
/// <param name="bucketType">Type of cooldown bucket. This allows controlling whether the bucket will be cooled down per user, guild, channel, or globally.</param>
public ContextMenuCooldownAttribute(int maxUses, double resetAfter, CooldownBucketType bucketType)
{
this.MaxUses = maxUses;
this.Reset = TimeSpan.FromSeconds(resetAfter);
this.BucketType = bucketType;
this._buckets = new ConcurrentDictionary<string, ContextMenuCooldownBucket>();
}
/// <summary>
/// Gets a cooldown bucket for given command context.
/// </summary>
/// <param name="ctx">Command context to get cooldown bucket for.</param>
/// <returns>Requested cooldown bucket, or null if one wasn't present.</returns>
public ContextMenuCooldownBucket GetBucket(BaseContext ctx)
{
var bid = this.GetBucketId(ctx, out _, out _, out _);
this._buckets.TryGetValue(bid, out var bucket);
return bucket;
}
/// <summary>
/// Calculates the cooldown remaining for given command context.
/// </summary>
/// <param name="ctx">Context for which to calculate the cooldown.</param>
/// <returns>Remaining cooldown, or zero if no cooldown is active.</returns>
public TimeSpan GetRemainingCooldown(BaseContext ctx)
{
var bucket = this.GetBucket(ctx);
return bucket == null ? TimeSpan.Zero : bucket.RemainingUses > 0 ? TimeSpan.Zero : bucket.ResetsAt - DateTimeOffset.UtcNow;
}
/// <summary>
/// Calculates bucket ID for given command context.
/// </summary>
/// <param name="ctx">Context for which to calculate bucket ID for.</param>
/// <param name="userId">ID of the user with which this bucket is associated.</param>
/// <param name="channelId">ID of the channel with which this bucket is associated.</param>
/// <param name="guildId">ID of the guild with which this bucket is associated.</param>
/// <returns>Calculated bucket ID.</returns>
private string GetBucketId(BaseContext 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 = CooldownBucket.MakeId(userId, channelId, guildId);
return bid;
}
/// <summary>
/// Executes a check.
/// </summary>
/// <param name="ctx">The command context.</param>
public override async Task<bool> ExecuteChecksAsync(BaseContext ctx)
{
var bid = this.GetBucketId(ctx, out var usr, out var chn, out var gld);
if (!this._buckets.TryGetValue(bid, out var bucket))
{
bucket = new ContextMenuCooldownBucket(this.MaxUses, this.Reset, usr, chn, gld);
this._buckets.AddOrUpdate(bid, bucket, (k, v) => bucket);
}
return await bucket.DecrementUseAsync().ConfigureAwait(false);
}
}
/// <summary>
/// Represents a cooldown bucket for commands.
/// </summary>
public sealed class ContextMenuCooldownBucket : CooldownBucket
{
internal ContextMenuCooldownBucket(int maxUses, TimeSpan resetAfter, ulong userId = 0, ulong channelId = 0, ulong guildId = 0)
: base(maxUses, resetAfter, userId, channelId, guildId)
{
}
/// <summary>
/// Returns a string representation of this command cooldown bucket.
/// </summary>
/// <returns>String representation of this command cooldown bucket.</returns>
public override string ToString() => $"Context Menu Command bucket {this.BucketId}";
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/DontInjectAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/DontInjectAttribute.cs
index a3261fea2..9992355eb 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/DontInjectAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/DontInjectAttribute.cs
@@ -1,32 +1,32 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Attributes;
/// <summary>
/// Prevents this field or property from having its value injected by dependency injection.
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class DontInjectAttribute : Attribute
{ }
diff --git a/DisCatSharp.ApplicationCommands/Attributes/RequireBotPermissionsAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/RequireBotPermissionsAttribute.cs
index 8a43aa1cb..457f79c05 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/RequireBotPermissionsAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/RequireBotPermissionsAttribute.cs
@@ -1,77 +1,77 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Threading.Tasks;
using DisCatSharp.ApplicationCommands.Context;
using DisCatSharp.Enums;
namespace DisCatSharp.ApplicationCommands.Attributes;
/// <summary>
/// Defines that usage of this application command is only possible when the bot is granted a specific permission.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class ApplicationCommandRequireBotPermissionsAttribute : ApplicationCommandCheckBaseAttribute
{
/// <summary>
/// Gets the permissions required by this attribute.
/// </summary>
public Permissions Permissions { get; }
/// <summary>
/// Gets or sets this check's behaviour in DMs. True means the check will always pass in DMs, whereas false means that it will always fail.
/// </summary>
public bool IgnoreDms { get; } = true;
/// <summary>
/// Defines that usage of this application command is only possible when the bot is granted a specific permission.
/// </summary>
/// <param name="permissions">Permissions required to execute this command.</param>
/// <param name="ignoreDms">Sets this check's behaviour in DMs. True means the check will always pass in DMs, whereas false means that it will always fail.</param>
public ApplicationCommandRequireBotPermissionsAttribute(Permissions permissions, bool ignoreDms = true)
{
this.Permissions = permissions;
this.IgnoreDms = ignoreDms;
}
/// <summary>
/// Runs checks.
/// </summary>
public override async Task<bool> ExecuteChecksAsync(BaseContext ctx)
{
if (ctx.Guild == null)
return this.IgnoreDms;
var bot = await ctx.Guild.GetMemberAsync(ctx.Client.CurrentUser.Id).ConfigureAwait(false);
if (bot == null)
return false;
if (bot.Id == ctx.Guild.OwnerId)
return true;
var pbot = ctx.Channel.PermissionsFor(bot);
return (pbot & Permissions.Administrator) != 0 || (pbot & this.Permissions) == this.Permissions;
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/RequireDirectMessageAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/RequireDirectMessageAttribute.cs
index 727c6c552..7f97aced7 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/RequireDirectMessageAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/RequireDirectMessageAttribute.cs
@@ -1,48 +1,48 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Threading.Tasks;
using DisCatSharp.ApplicationCommands.Context;
using DisCatSharp.Entities;
namespace DisCatSharp.ApplicationCommands.Attributes;
/// <summary>
/// Defines that this application command is only usable within a direct message channel.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class ApplicationCommandRequireDirectMessageAttribute : ApplicationCommandCheckBaseAttribute
{
/// <summary>
/// Defines that this command is only usable within a direct message channel.
/// </summary>
public ApplicationCommandRequireDirectMessageAttribute()
{ }
/// <summary>
/// Runs checks.
/// </summary>
public override Task<bool> ExecuteChecksAsync(BaseContext ctx)
=> Task.FromResult(ctx.Channel is DiscordDmChannel);
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/RequireDisCatSharpDeveloperAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/RequireDisCatSharpDeveloperAttribute.cs
index fb027e06f..646126a8d 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/RequireDisCatSharpDeveloperAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/RequireDisCatSharpDeveloperAttribute.cs
@@ -1,47 +1,47 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Threading.Tasks;
using DisCatSharp.ApplicationCommands.Context;
namespace DisCatSharp.ApplicationCommands.Attributes;
/// <summary>
/// Defines that this application command is restricted to the owner of the bot.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class ApplicationCommandRequireDisCatSharpDeveloperAttribute : ApplicationCommandCheckBaseAttribute
{
/// <summary>
/// Defines that this application command is restricted to the owner of the bot.
/// </summary>
public ApplicationCommandRequireDisCatSharpDeveloperAttribute()
{ }
/// <summary>
/// Runs checks.
/// </summary>
public override Task<bool> ExecuteChecksAsync(BaseContext ctx)
=> Task.FromResult(true);
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/RequireGuildAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/RequireGuildAttribute.cs
index c5c94dd43..bc265a081 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/RequireGuildAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/RequireGuildAttribute.cs
@@ -1,47 +1,47 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Threading.Tasks;
using DisCatSharp.ApplicationCommands.Context;
namespace DisCatSharp.ApplicationCommands.Attributes;
/// <summary>
/// Defines that this application command is only usable within a guild.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class ApplicationCommandRequireGuildAttribute : ApplicationCommandCheckBaseAttribute
{
/// <summary>
/// Defines that this command is only usable within a guild.
/// </summary>
public ApplicationCommandRequireGuildAttribute()
{ }
/// <summary>
/// Runs checks.
/// </summary>
public override Task<bool> ExecuteChecksAsync(BaseContext ctx)
=> Task.FromResult(ctx.Guild != null);
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/RequireNsfwAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/RequireNsfwAttribute.cs
index afc052c2a..ce906d3dc 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/RequireNsfwAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/RequireNsfwAttribute.cs
@@ -1,47 +1,47 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Threading.Tasks;
using DisCatSharp.ApplicationCommands.Context;
namespace DisCatSharp.ApplicationCommands.Attributes;
/// <summary>
/// Defines that this application command is only usable within a guild.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class ApplicationCommandRequireNsfwAttribute : ApplicationCommandCheckBaseAttribute
{
/// <summary>
/// Defines that this command is only usable within a guild.
/// </summary>
public ApplicationCommandRequireNsfwAttribute()
{ }
/// <summary>
/// Runs checks.
/// </summary>
public override Task<bool> ExecuteChecksAsync(BaseContext ctx)
=> Task.FromResult(ctx.Guild == null || ctx.Channel.IsNsfw);
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/RequireOwnerAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/RequireOwnerAttribute.cs
index 09596460a..8a80b3487 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/RequireOwnerAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/RequireOwnerAttribute.cs
@@ -1,53 +1,53 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
using System.Threading.Tasks;
using DisCatSharp.ApplicationCommands.Context;
namespace DisCatSharp.ApplicationCommands.Attributes;
/// <summary>
/// Defines that this application command is restricted to the owner of the bot.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class ApplicationCommandRequireOwnerAttribute : ApplicationCommandCheckBaseAttribute
{
/// <summary>
/// Defines that this application command is restricted to the owner of the bot.
/// </summary>
public ApplicationCommandRequireOwnerAttribute()
{ }
/// <summary>
/// Runs checks.
/// </summary>
public override Task<bool> ExecuteChecksAsync(BaseContext ctx)
{
var app = ctx.Client.CurrentApplication;
var me = ctx.Client.CurrentUser;
return app != null ? Task.FromResult(app.Owners.Any(x => x.Id == ctx.User.Id)) : Task.FromResult(ctx.User.Id == me.Id);
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/RequireOwnerOrIdAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/RequireOwnerOrIdAttribute.cs
index 0573935b7..b243bda6a 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/RequireOwnerOrIdAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/RequireOwnerOrIdAttribute.cs
@@ -1,69 +1,69 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp;
using DisCatSharp.ApplicationCommands.Context;
namespace DisCatSharp.ApplicationCommands.Attributes;
/// <summary>
/// Requires ownership of the bot or a whitelisted id to execute this command.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class ApplicationCommandRequireOwnerOrIdAttribute : ApplicationCommandCheckBaseAttribute
{
/// <summary>
/// Allowed user ids
/// </summary>
public IReadOnlyList<ulong> UserIds { get; }
/// <summary>
/// Defines that usage of this command is restricted to the owner or whitelisted ids of the bot.
/// </summary>
/// <param name="userIds">List of allowed user ids</param>
public ApplicationCommandRequireOwnerOrIdAttribute(params ulong[] userIds)
{
this.UserIds = new ReadOnlyCollection<ulong>(userIds);
}
/// <summary>
/// Executes the a check.
/// </summary>
/// <param name="ctx">The command context.</param>s
public override Task<bool> ExecuteChecksAsync(BaseContext 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/RequirePermissionsAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/RequirePermissionsAttribute.cs
index 898b57e34..7abfc9292 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/RequirePermissionsAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/RequirePermissionsAttribute.cs
@@ -1,87 +1,87 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Threading.Tasks;
using DisCatSharp.ApplicationCommands.Context;
using DisCatSharp.Enums;
namespace DisCatSharp.ApplicationCommands.Attributes;
/// <summary>
/// Defines that usage of this application command is restricted to members with specified permissions. This check also verifies that the bot has the same permissions.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class ApplicationCommandRequirePermissionsAttribute : ApplicationCommandCheckBaseAttribute
{
/// <summary>
/// Gets the permissions required by this attribute.
/// </summary>
public Permissions Permissions { get; }
/// <summary>
/// Gets or sets this check's behaviour in DMs. True means the check will always pass in DMs, whereas false means that it will always fail.
/// </summary>
public bool IgnoreDms { get; } = true;
/// <summary>
/// Defines that usage of this command is restricted to members with specified permissions. This check also verifies that the bot has the same permissions.
/// </summary>
/// <param name="permissions">Permissions required to execute this command.</param>
/// <param name="ignoreDms">Sets this check's behaviour in DMs. True means the check will always pass in DMs, whereas false means that it will always fail.</param>
public ApplicationCommandRequirePermissionsAttribute(Permissions permissions, bool ignoreDms = true)
{
this.Permissions = permissions;
this.IgnoreDms = ignoreDms;
}
/// <summary>
/// Runs checks.
/// </summary>
public override async Task<bool> ExecuteChecksAsync(BaseContext ctx)
{
if (ctx.Guild == null)
return this.IgnoreDms;
var usr = ctx.Member;
if (usr == null)
return false;
var pusr = ctx.Channel.PermissionsFor(usr);
var bot = await ctx.Guild.GetMemberAsync(ctx.Client.CurrentUser.Id).ConfigureAwait(false);
if (bot == null)
return false;
var pbot = ctx.Channel.PermissionsFor(bot);
var usrok = ctx.Guild.OwnerId == usr.Id;
var botok = ctx.Guild.OwnerId == bot.Id;
if (!usrok)
usrok = (pusr & Permissions.Administrator) != 0 || (pusr & this.Permissions) == this.Permissions;
if (!botok)
botok = (pbot & Permissions.Administrator) != 0 || (pbot & this.Permissions) == this.Permissions;
return usrok && botok;
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/RequireUserPermissionsAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/RequireUserPermissionsAttribute.cs
index 79b116fe1..b25447263 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/RequireUserPermissionsAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/RequireUserPermissionsAttribute.cs
@@ -1,79 +1,79 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Threading.Tasks;
using DisCatSharp.ApplicationCommands.Context;
using DisCatSharp.Enums;
namespace DisCatSharp.ApplicationCommands.Attributes;
/// <summary>
/// Defines that usage of this application command is restricted to members with specified permissions.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class ApplicationCommandRequireUserPermissionsAttribute : ApplicationCommandCheckBaseAttribute
{
/// <summary>
/// Gets the permissions required by this attribute.
/// </summary>
public Permissions Permissions { get; }
/// <summary>
/// Gets or sets this check's behaviour in DMs. True means the check will always pass in DMs, whereas false means that it will always fail.
/// </summary>
public bool IgnoreDms { get; } = true;
/// <summary>
/// Defines that usage of this command is restricted to members with specified permissions.
/// </summary>
/// <param name="permissions">Permissions required to execute this command.</param>
/// <param name="ignoreDms">Sets this check's behaviour in DMs. True means the check will always pass in DMs, whereas false means that it will always fail.</param>
public ApplicationCommandRequireUserPermissionsAttribute(Permissions permissions, bool ignoreDms = true)
{
this.Permissions = permissions;
this.IgnoreDms = ignoreDms;
}
/// <summary>
/// Runs checks.
/// </summary>
public override Task<bool> ExecuteChecksAsync(BaseContext ctx)
{
if (ctx.Guild == null)
return Task.FromResult(this.IgnoreDms);
var usr = ctx.Member;
if (usr == null)
return Task.FromResult(false);
if (usr.Id == ctx.Guild.OwnerId)
return Task.FromResult(true);
var pusr = ctx.Channel.PermissionsFor(usr);
return (pusr & Permissions.Administrator) != 0
? Task.FromResult(true)
: (pusr & this.Permissions) == this.Permissions ? Task.FromResult(true) : Task.FromResult(false);
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/AutocompleteAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/AutocompleteAttribute.cs
index a64df8873..a8c0f2903 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/AutocompleteAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/AutocompleteAttribute.cs
@@ -1,46 +1,46 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Attributes;
/// <summary>
/// The autocomplete attribute.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public class AutocompleteAttribute : Attribute
{
/// <summary>
/// The type of the provider.
/// </summary>
public Type ProviderType { get; }
/// <summary>
/// Adds an autocomplete provider to this command option.
/// </summary>
/// <param name="providerType">The type of the provider.</param>
public AutocompleteAttribute(Type providerType)
{
this.ProviderType = providerType;
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChannelTypesAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChannelTypesAttribute.cs
index 43005808a..cb1b580a7 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChannelTypesAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChannelTypesAttribute.cs
@@ -1,50 +1,50 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.Enums;
namespace DisCatSharp.ApplicationCommands.Attributes;
/// <summary>
/// Defines allowed channel types for a channel parameter.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public class ChannelTypesAttribute : Attribute
{
/// <summary>
/// Allowed channel types.
/// </summary>
public List<ChannelType> ChannelTypes { get; }
/// <summary>
/// Defines allowed channel types for a channel parameter.
/// </summary>
/// <param name="channelTypes">The channel types to allow.</param>
public ChannelTypesAttribute(params ChannelType[] channelTypes)
{
this.ChannelTypes = channelTypes.ToList();
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceAttribute.cs
index 2a4da0f8a..1c98b585a 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceAttribute.cs
@@ -1,86 +1,86 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Attributes;
/// <summary>
/// Adds a choice for this slash command option
/// </summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true)]
public class ChoiceAttribute : Attribute
{
/// <summary>
/// Gets the name of the choice
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the value of the choice
/// </summary>
public object Value { get; }
/// <summary>
/// Adds a choice to the slash command option
/// </summary>
/// <param name="name">The name of the choice.</param>
/// <param name="value">The value of the choice.</param>
public ChoiceAttribute(string name, string value)
{
this.Name = name;
this.Value = value;
}
/// <summary>
/// Adds a choice to the slash command option
/// </summary>
/// <param name="name">The name of the choice.</param>
/// <param name="value">The value of the choice.</param>
public ChoiceAttribute(string name, long value)
{
this.Name = name;
this.Value = value;
}
/// <summary>
/// Adds a choice to the slash command option
/// </summary>
/// <param name="name">The name of the choice.</param>
/// <param name="value">The value of the choice.</param>
public ChoiceAttribute(string name, int value)
{
this.Name = name;
this.Value = value;
}
/// <summary>
/// Adds a choice to the slash command option
/// </summary>
/// <param name="name">The name of the choice.</param>
/// <param name="value">The value of the choice.</param>
public ChoiceAttribute(string name, double value)
{
this.Name = name;
this.Value = value;
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceNameAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceNameAttribute.cs
index 37a5b8de0..bebc314b3 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceNameAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceNameAttribute.cs
@@ -1,46 +1,46 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Attributes;
/// <summary>
/// Sets the name for this enum choice.
/// </summary>
[AttributeUsage(AttributeTargets.All)]
public class ChoiceNameAttribute : Attribute
{
/// <summary>
/// The name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Sets the name for this enum choice.
/// </summary>
/// <param name="name">The name for this enum choice.</param>
public ChoiceNameAttribute(string name)
{
this.Name = name;
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceProvider.cs b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceProvider.cs
index dd7e57d5e..9cc899ef5 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceProvider.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceProvider.cs
@@ -1,50 +1,50 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
namespace DisCatSharp.ApplicationCommands.Attributes;
/// <summary>
/// Implementation of <see cref="IChoiceProvider"/> with access to service collection.
/// </summary>
public abstract class ChoiceProvider : IChoiceProvider
{
/// <summary>
/// Sets the choices for the slash command.
/// </summary>
public abstract Task<IEnumerable<DiscordApplicationCommandOptionChoice>> Provider();
/// <summary>
/// Sets the service provider.
/// </summary>
public IServiceProvider Services { get; set; }
/// <summary>
/// The optional ID of the Guild the command got registered for.
/// </summary>
public ulong? GuildId { get; set; }
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceProviderAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceProviderAttribute.cs
index 66ca0157b..88fc56d8a 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceProviderAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/ChoiceProviderAttribute.cs
@@ -1,48 +1,48 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Attributes;
/// <summary>
/// Sets a IChoiceProvider for a command options. ChoiceProviders can be used to provide
/// DiscordApplicationCommandOptionChoice from external sources such as a database.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true)]
public class ChoiceProviderAttribute : Attribute
{
/// <summary>
/// The type of the provider.
/// </summary>
public Type ProviderType { get; }
/// <summary>
/// Adds a choice provider to this command.
/// </summary>
/// <param name="providerType">The type of the provider.</param>
public ChoiceProviderAttribute(Type providerType)
{
this.ProviderType = providerType;
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/IAutocompleteProvider.cs b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/IAutocompleteProvider.cs
index 5b1aa6edc..33e811e2d 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/IAutocompleteProvider.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/IAutocompleteProvider.cs
@@ -1,41 +1,41 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.ApplicationCommands.Context;
using DisCatSharp.Entities;
namespace DisCatSharp.ApplicationCommands.Attributes;
/// <summary>
/// The autocomplete provider.
/// </summary>
public interface IAutocompleteProvider
{
/// <summary>
/// Provider the autocompletion.
/// </summary>
/// <param name="context">The context.</param>
Task<IEnumerable<DiscordApplicationCommandAutocompleteChoice>> Provider(AutocompleteContext context);
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/IChoiceProvider.cs b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/IChoiceProvider.cs
index efa6f67b0..f7e79c576 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/IChoiceProvider.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/IChoiceProvider.cs
@@ -1,39 +1,39 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Entities;
namespace DisCatSharp.ApplicationCommands.Attributes;
/// <summary>
/// All choice providers must inherit from this interface
/// </summary>
public interface IChoiceProvider
{
/// <summary>
/// Sets the choices for the slash command
/// </summary>
Task<IEnumerable<DiscordApplicationCommandOptionChoice>> Provider();
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/MinimumMaximumAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/MinimumMaximumAttribute.cs
index 245ef4d5c..6fb24d5de 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/MinimumMaximumAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/MinimumMaximumAttribute.cs
@@ -1,142 +1,142 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Attributes;
/// <summary>
/// Sets a minimum value for this slash command option. Only valid for <see cref="int"/>, <see cref="long"/> or <see cref="double"/> parameters.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public class MinimumValueAttribute : Attribute
{
/// <summary>
/// The value.
/// </summary>
public object Value { get; internal set; }
/// <summary>
/// Sets a minimum value for this slash command option. Only valid for <see cref="int"/>, <see cref="long"/> or <see cref="double"/> parameters.
/// </summary>
public MinimumValueAttribute(int value)
{
this.Value = value;
}
/// <summary>
/// Sets a minimum value for this slash command option. Only valid for <see cref="int"/>, <see cref="long"/> or <see cref="double"/> parameters.
/// </summary>
public MinimumValueAttribute(long value)
{
this.Value = value;
}
/// <summary>
/// Sets a minimum value for this slash command option. Only valid for <see cref="int"/>, <see cref="long"/> or <see cref="double"/> parameters.
/// </summary>
public MinimumValueAttribute(double value)
{
this.Value = value;
}
}
/// <summary>
/// Sets a maximum value for this slash command option. Only valid for <see cref="int"/>, <see cref="long"/> or <see cref="double"/> parameters.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public class MaximumValueAttribute : Attribute
{
/// <summary>
/// The value.
/// </summary>
public object Value { get; internal set; }
/// <summary>
/// Sets a maximum value for this slash command option. Only valid for <see cref="int"/>, <see cref="long"/> or <see cref="double"/> parameters.
/// </summary>
public MaximumValueAttribute(int value)
{
this.Value = value;
}
/// <summary>
/// Sets a maximum value for this slash command option. Only valid for <see cref="int"/>, <see cref="long"/> or <see cref="double"/> parameters.
/// </summary>
public MaximumValueAttribute(long value)
{
this.Value = value;
}
/// <summary>
/// Sets a maximum value for this slash command option. Only valid for <see cref="int"/>, <see cref="long"/> or <see cref="double"/> parameters.
/// </summary>
public MaximumValueAttribute(double value)
{
this.Value = value;
}
}
/// <summary>
/// Sets a minimum value for this slash command option. Only valid for <see cref="string"/> parameters.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public class MinimumLengthAttribute : Attribute
{
/// <summary>
/// The value.
/// </summary>
public int? Value { get; internal set; }
/// <summary>
/// Sets a minimum value for this slash command option. Only valid for <see cref="string"/> parameters.
/// </summary>
public MinimumLengthAttribute(int value)
{
if (value > 600)
throw new ArgumentException("Minimum cannot be more than 6000.");
this.Value = value;
}
}
/// <summary>
/// Sets a maximum value for this slash command option. Only valid for <see cref="string"/> parameters.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public class MaximumLengthAttribute : Attribute
{
/// <summary>
/// The value.
/// </summary>
public int? Value { get; internal set; }
/// <summary>
/// Sets a maximum value for this slash command option. Only valid for <see cref="string"/> parameters.
/// </summary>
public MaximumLengthAttribute(int value)
{
if (value == 0 || value > 600)
throw new ArgumentException("Maximum length cannot be less than 1 and cannot be more than 6000.");
this.Value = value;
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/OptionAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/OptionAttribute.cs
index 50bb1af4a..93c36f80b 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/OptionAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/OptionAttribute.cs
@@ -1,65 +1,65 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Attributes;
/// <summary>
/// Marks this parameter as an option for a slash command
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public class OptionAttribute : Attribute
{
/// <summary>
/// Gets the name of this option.
/// </summary>
public string Name;
/// <summary>
/// Gets the description of this option.
/// </summary>
public string Description;
/// <summary>
/// Whether to autocomplete this option.
/// </summary>
public bool Autocomplete;
/// <summary>
/// Initializes a new instance of the <see cref="OptionAttribute"/> class.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="description">The description.</param>
/// <param name="autocomplete">If true, autocomplete.</param>
public OptionAttribute(string name, string description, bool autocomplete = false)
{
if (name.Length > 32)
throw new ArgumentException("Slash command option names cannot go over 32 characters.");
else if (description.Length > 100)
throw new ArgumentException("Slash command option descriptions cannot go over 100 characters.");
this.Name = name.ToLower();
this.Description = description;
this.Autocomplete = autocomplete;
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandAttribute.cs
index 3f4a6f9e3..ac943191b 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandAttribute.cs
@@ -1,123 +1,123 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
namespace DisCatSharp.ApplicationCommands.Attributes;
/// <summary>
/// Marks this method as a slash command
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class SlashCommandAttribute : Attribute
{
/// <summary>
/// Gets the name of this command
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets the description of this command
/// </summary>
public string Description { get; set; }
/// <summary>
/// Gets the needed permission of this command
/// </summary>
public Permissions? DefaultMemberPermissions { get; set; }
/// <summary>
/// Gets the dm permission of this command
/// </summary>
public bool? DmPermission { get; set; }
/// <summary>
/// Gets whether this command is marked as NSFW
/// </summary>
public bool IsNsfw { get; set; }
/// <summary>
/// Marks this method as a slash command
/// </summary>
/// <param name="name">The name of this slash command.</param>
/// <param name="description">The description of this slash command.</param>
/// <param name="isNsfw">Whether this command is marked as NSFW.</param>
public SlashCommandAttribute(string name, string description, bool isNsfw = false)
{
this.Name = name.ToLower();
this.Description = description;
this.DefaultMemberPermissions = null;
this.DmPermission = null;
this.IsNsfw = isNsfw;
}
/// <summary>
/// Marks this method as a slash command
/// </summary>
/// <param name="name">The name of this slash command.</param>
/// <param name="description">The description of this slash command.</param>
/// <param name="defaultMemberPermissions">The default member permissions.</param>
/// <param name="isNsfw">Whether this command is marked as NSFW.</param>
public SlashCommandAttribute(string name, string description, long defaultMemberPermissions, bool isNsfw = false)
{
this.Name = name.ToLower();
this.Description = description;
this.DefaultMemberPermissions = (Permissions)defaultMemberPermissions;
this.DmPermission = null;
this.IsNsfw = isNsfw;
}
/// <summary>
/// Marks this method as a slash command
/// </summary>
/// <param name="name">The name of this slash command.</param>
/// <param name="description">The description of this slash command.</param>
/// <param name="dmPermission">The dm permission.</param>
/// <param name="isNsfw">Whether this command is marked as NSFW.</param>
public SlashCommandAttribute(string name, string description, bool dmPermission, bool isNsfw = false)
{
this.Name = name.ToLower();
this.Description = description;
this.DefaultMemberPermissions = null;
this.DmPermission = dmPermission;
this.IsNsfw = isNsfw;
}
/// <summary>
/// Marks this method as a slash command
/// </summary>
/// <param name="name">The name of this slash command.</param>
/// <param name="description">The description of this slash command.</param>
/// <param name="defaultMemberPermissions">The default member permissions.</param>
/// <param name="dmPermission">The dm permission.</param>
/// <param name="isNsfw">Whether this command is marked as NSFW.</param>
public SlashCommandAttribute(string name, string description, long defaultMemberPermissions, bool dmPermission, bool isNsfw = false)
{
this.Name = name.ToLower();
this.Description = description;
this.DefaultMemberPermissions = (Permissions)defaultMemberPermissions;
this.DmPermission = dmPermission;
this.IsNsfw = isNsfw;
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandCooldownAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandCooldownAttribute.cs
index 21faddcde..a2ffc216e 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandCooldownAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandCooldownAttribute.cs
@@ -1,157 +1,157 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Tasks;
using DisCatSharp.ApplicationCommands.Context;
using DisCatSharp.ApplicationCommands.Entities;
using DisCatSharp.ApplicationCommands.Enums;
namespace DisCatSharp.ApplicationCommands.Attributes;
/// <summary>
/// Defines a cooldown for this command. This allows you to define how many times can users execute a specific command
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public sealed class SlashCommandCooldownAttribute : ApplicationCommandCheckBaseAttribute, ICooldown<BaseContext, SlashCommandCooldownBucket>
{
/// <summary>
/// Gets the maximum number of uses before this command triggers a cooldown for its bucket.
/// </summary>
public int MaxUses { get; }
/// <summary>
/// Gets the time after which the cooldown is reset.
/// </summary>
public TimeSpan Reset { get; }
/// <summary>
/// Gets the type of the cooldown bucket. This determines how cooldowns are applied.
/// </summary>
public CooldownBucketType BucketType { get; }
/// <summary>
/// Gets the cooldown buckets for this command.
/// </summary>
internal readonly ConcurrentDictionary<string, SlashCommandCooldownBucket> _buckets;
/// <summary>
/// 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.
/// </summary>
/// <param name="maxUses">Number of times the command can be used before triggering a cooldown.</param>
/// <param name="resetAfter">Number of seconds after which the cooldown is reset.</param>
/// <param name="bucketType">Type of cooldown bucket. This allows controlling whether the bucket will be cooled down per user, guild, channel, or globally.</param>
public SlashCommandCooldownAttribute(int maxUses, double resetAfter, CooldownBucketType bucketType)
{
this.MaxUses = maxUses;
this.Reset = TimeSpan.FromSeconds(resetAfter);
this.BucketType = bucketType;
this._buckets = new ConcurrentDictionary<string, SlashCommandCooldownBucket>();
}
/// <summary>
/// Gets a cooldown bucket for given command context.
/// </summary>
/// <param name="ctx">Command context to get cooldown bucket for.</param>
/// <returns>Requested cooldown bucket, or null if one wasn't present.</returns>
public SlashCommandCooldownBucket GetBucket(BaseContext ctx)
{
var bid = this.GetBucketId(ctx, out _, out _, out _);
this._buckets.TryGetValue(bid, out var bucket);
return bucket;
}
/// <summary>
/// Calculates the cooldown remaining for given command context.
/// </summary>
/// <param name="ctx">Context for which to calculate the cooldown.</param>
/// <returns>Remaining cooldown, or zero if no cooldown is active.</returns>
public TimeSpan GetRemainingCooldown(BaseContext ctx)
{
var bucket = this.GetBucket(ctx);
return bucket == null ? TimeSpan.Zero : bucket.RemainingUses > 0 ? TimeSpan.Zero : bucket.ResetsAt - DateTimeOffset.UtcNow;
}
/// <summary>
/// Calculates bucket ID for given command context.
/// </summary>
/// <param name="ctx">Context for which to calculate bucket ID for.</param>
/// <param name="userId">ID of the user with which this bucket is associated.</param>
/// <param name="channelId">ID of the channel with which this bucket is associated.</param>
/// <param name="guildId">ID of the guild with which this bucket is associated.</param>
/// <returns>Calculated bucket ID.</returns>
private string GetBucketId(BaseContext 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 = CooldownBucket.MakeId(userId, channelId, guildId);
return bid;
}
/// <summary>
/// Executes a check.
/// </summary>
/// <param name="ctx">The command context.</param>
public override async Task<bool> ExecuteChecksAsync(BaseContext ctx)
{
var bid = this.GetBucketId(ctx, out var usr, out var chn, out var gld);
if (!this._buckets.TryGetValue(bid, out var bucket))
{
bucket = new SlashCommandCooldownBucket(this.MaxUses, this.Reset, usr, chn, gld);
this._buckets.AddOrUpdate(bid, bucket, (k, v) => bucket);
}
return await bucket.DecrementUseAsync().ConfigureAwait(false);
}
}
/// <summary>
/// Represents a cooldown bucket for commands.
/// </summary>
public sealed class SlashCommandCooldownBucket : CooldownBucket
{
/// <summary>
/// Returns a string representation of this command cooldown bucket.
/// </summary>
/// <returns>String representation of this command cooldown bucket.</returns>
public override string ToString() => $"Slash Command bucket {this.BucketId}";
internal SlashCommandCooldownBucket(int maxUses, TimeSpan resetAfter, ulong userId = 0, ulong channelId = 0, ulong guildId = 0)
: base(maxUses, resetAfter, userId, channelId, guildId)
{
}
}
diff --git a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandGroupAttribute.cs b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandGroupAttribute.cs
index e7a1814d2..a51bf89dd 100644
--- a/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandGroupAttribute.cs
+++ b/DisCatSharp.ApplicationCommands/Attributes/SlashCommand/SlashCommandGroupAttribute.cs
@@ -1,123 +1,123 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
namespace DisCatSharp.ApplicationCommands.Attributes;
/// <summary>
/// Marks this class a slash command group
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class SlashCommandGroupAttribute : Attribute
{
/// <summary>
/// Gets the name of this slash command group
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets the description of this slash command group
/// </summary>
public string Description { get; set; }
/// <summary>
/// Gets the needed permission of this slash command group
/// </summary>
public Permissions? DefaultMemberPermissions { get; set; }
/// <summary>
/// Gets the dm permission of this slash command group
/// </summary>
public bool? DmPermission { get; set; }
/// <summary>
/// Gets whether this command is marked as NSFW
/// </summary>
public bool IsNsfw { get; set; }
/// <summary>
/// Marks this class as a slash command group
/// </summary>
/// <param name="name">The name of this slash command group.</param>
/// <param name="description">The description of this slash command group.</param>
/// <param name="isNsfw">Whether this command is marked as NSFW.</param>
public SlashCommandGroupAttribute(string name, string description, bool isNsfw = false)
{
this.Name = name.ToLower();
this.Description = description;
this.DefaultMemberPermissions = null;
this.DmPermission = null;
this.IsNsfw = isNsfw;
}
/// <summary>
/// Marks this method as a slash command group
/// </summary>
/// <param name="name">The name of this slash command.</param>
/// <param name="description">The description of this slash command.</param>
/// <param name="defaultMemberPermissions">The default member permissions.</param>
/// <param name="isNsfw">Whether this command is marked as NSFW.</param>
public SlashCommandGroupAttribute(string name, string description, long defaultMemberPermissions, bool isNsfw = false)
{
this.Name = name.ToLower();
this.Description = description;
this.DefaultMemberPermissions = (Permissions)defaultMemberPermissions;
this.DmPermission = null;
this.IsNsfw = isNsfw;
}
/// <summary>
/// Marks this method as a slash command group
/// </summary>
/// <param name="name">The name of this slash command.</param>
/// <param name="description">The description of this slash command.</param>
/// <param name="dmPermission">The dm permission.</param>
/// <param name="isNsfw">Whether this command is marked as NSFW.</param>
public SlashCommandGroupAttribute(string name, string description, bool dmPermission, bool isNsfw = false)
{
this.Name = name.ToLower();
this.Description = description;
this.DefaultMemberPermissions = null;
this.DmPermission = dmPermission;
this.IsNsfw = isNsfw;
}
/// <summary>
/// Marks this method as a slash command group
/// </summary>
/// <param name="name">The name of this slash command.</param>
/// <param name="description">The description of this slash command.</param>
/// <param name="defaultMemberPermissions">The default member permissions.</param>
/// <param name="dmPermission">The dm permission.</param>
/// <param name="isNsfw">Whether this command is marked as NSFW.</param>
public SlashCommandGroupAttribute(string name, string description, long defaultMemberPermissions, bool dmPermission, bool isNsfw = false)
{
this.Name = name.ToLower();
this.Description = description;
this.DefaultMemberPermissions = (Permissions)defaultMemberPermissions;
this.DmPermission = dmPermission;
this.IsNsfw = isNsfw;
}
}
diff --git a/DisCatSharp.ApplicationCommands/Checks/ApplicationCommandEqualityChecks.cs b/DisCatSharp.ApplicationCommands/Checks/ApplicationCommandEqualityChecks.cs
index 9308fa485..490746d03 100644
--- a/DisCatSharp.ApplicationCommands/Checks/ApplicationCommandEqualityChecks.cs
+++ b/DisCatSharp.ApplicationCommands/Checks/ApplicationCommandEqualityChecks.cs
@@ -1,327 +1,327 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.Entities;
using DisCatSharp.Enums;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace DisCatSharp.ApplicationCommands.Checks;
internal static class ApplicationCommandEqualityChecks
{
/// <summary>
/// Whether two application commands are equal.
/// </summary>
/// <param name="ac1">Source command.</param>
/// <param name="targetApplicationCommand">Command to check against.</param>
/// <param name="client">The discord client.</param>
/// <param name="IsGuild">Whether the equal check is performed for a guild command.</param>
internal static bool IsEqualTo(this DiscordApplicationCommand ac1, DiscordApplicationCommand targetApplicationCommand, DiscordClient client, bool IsGuild)
{
if (targetApplicationCommand is null || ac1 is null)
return false;
DiscordApplicationCommand sourceApplicationCommand = new(
ac1.Name, ac1.Description, ac1.Options,
ac1.Type,
ac1.NameLocalizations, ac1.DescriptionLocalizations,
ac1.DefaultMemberPermissions, ac1.DmPermission ?? true, ac1.IsNsfw
);
if (sourceApplicationCommand.DefaultMemberPermissions == Permissions.None && targetApplicationCommand.DefaultMemberPermissions == null)
sourceApplicationCommand.DefaultMemberPermissions = null;
if (IsGuild)
{
sourceApplicationCommand.DmPermission = null;
targetApplicationCommand.DmPermission = null;
}
client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, "[AC Change Check] Command {name}\n\n[{jsonOne},{jsontwo}]\n\n", ac1.Name, JsonConvert.SerializeObject(sourceApplicationCommand), JsonConvert.SerializeObject(targetApplicationCommand));
return ac1.Type == targetApplicationCommand.Type && sourceApplicationCommand.SoftEqual(targetApplicationCommand, ac1.Type, ApplicationCommandsExtension.Configuration?.EnableLocalization ?? false, IsGuild);
}
/// <summary>
/// Checks softly whether two <see cref="DiscordApplicationCommand"/>s are the same.
/// Excluding id, application id and version here.
/// </summary>
/// <param name="source">Source application command.</param>
/// <param name="target">Application command to check against.</param>
/// <param name="type">The application command type.</param>
/// <param name="localizationEnabled">Whether localization is enabled.</param>
/// <param name="guild">Whether the equal check is performed for a guild command.</param>
internal static bool SoftEqual(this DiscordApplicationCommand source, DiscordApplicationCommand target, ApplicationCommandType type, bool localizationEnabled = false, bool guild = false)
{
bool? sDmPerm = source.DmPermission ?? true;
bool? tDmPerm = target.DmPermission ?? true;
if (guild)
{
sDmPerm = null;
tDmPerm = null;
}
return localizationEnabled
? type switch
{
ApplicationCommandType.ChatInput => DeepEqual(source, target, true, sDmPerm, tDmPerm),
_ => source.Name == target.Name
&& source.Type == target.Type && source.NameLocalizations == target.NameLocalizations
&& source.DefaultMemberPermissions == target.DefaultMemberPermissions
&& sDmPerm == tDmPerm && source.IsNsfw == target.IsNsfw
}
: type switch
{
ApplicationCommandType.ChatInput => DeepEqual(source, target, false, sDmPerm, tDmPerm),
_ => source.Name == target.Name
&& source.Type == target.Type
&& source.DefaultMemberPermissions == target.DefaultMemberPermissions
&& sDmPerm == tDmPerm && source.IsNsfw == target.IsNsfw
};
}
/// <summary>
/// Checks deeply whether two <see cref="DiscordApplicationCommand"/>s are the same.
/// Excluding id, application id and version here.
/// </summary>
/// <param name="source">Source application command.</param>
/// <param name="target">Application command to check against.</param>
/// <param name="localizationEnabled">Whether localization is enabled.</param>
/// <param name="sDmPerm">The source dm permission.</param>
/// <param name="tDmPerm">The target dm permission.</param>
internal static bool DeepEqual(DiscordApplicationCommand source, DiscordApplicationCommand target, bool localizationEnabled = false, bool? sDmPerm = null, bool? tDmPerm = null)
{
var rootCheck = true;
/*Console.WriteLine($"{source.Name == target.Name}");
Console.WriteLine($"{source.Description == target.Description}");
Console.WriteLine($"{source.Type == target.Type}");
Console.WriteLine($"{source.DefaultMemberPermissions == target.DefaultMemberPermissions} - {source.DefaultMemberPermissions} == {target.DefaultMemberPermissions}");
Console.WriteLine($"{sDmPerm == tDmPerm}");*/
rootCheck = source.Name == target.Name && source.Description == target.Description && source.Type == target.Type && source.DefaultMemberPermissions == target.DefaultMemberPermissions && sDmPerm == tDmPerm && source.IsNsfw == target.IsNsfw;
if (localizationEnabled)
rootCheck = rootCheck && source.NameLocalizations == target.NameLocalizations && source.DescriptionLocalizations == target.DescriptionLocalizations;
//Console.WriteLine($"{rootCheck}");
if (source.Options == null && target.Options == null)
return rootCheck;
else if ((source.Options != null && target.Options == null) || (source.Options == null && target.Options != null))
return false;
else if (source.Options.Any(o => o.Type == ApplicationCommandOptionType.SubCommandGroup) && target.Options.Any(o => o.Type == ApplicationCommandOptionType.SubCommandGroup))
{
List<DiscordApplicationCommandOption> minimalSourceOptions = new();
List<DiscordApplicationCommandOption> minimalTargetOptions = new();
foreach (var option in source.Options)
{
List<DiscordApplicationCommandOption> minimalSubSourceOptions = new();
if (option.Options != null)
{
foreach (var subOption in option.Options)
{
List<DiscordApplicationCommandOption> minimalSubSubSourceOptions = null;
if (subOption.Options != null)
{
minimalSubSubSourceOptions = new();
foreach (var subSubOption in subOption.Options)
minimalSubSubSourceOptions.Add(new DiscordApplicationCommandOption(
subSubOption.Name, subSubOption.Description, subSubOption.Type, subSubOption.Required,
subSubOption.Choices, null, subSubOption.ChannelTypes, subSubOption.AutoComplete,
subSubOption.MinimumValue, subSubOption.MaximumValue,
localizationEnabled ? subSubOption.NameLocalizations : null,
localizationEnabled ? subSubOption.DescriptionLocalizations : null,
subSubOption.MinimumLength, subSubOption.MaximumLength
));
minimalSubSourceOptions.Add(new DiscordApplicationCommandOption(
subOption.Name, subOption.Description, subOption.Type,
options: minimalSubSubSourceOptions,
nameLocalizations: localizationEnabled ? subOption.NameLocalizations : null,
descriptionLocalizations: localizationEnabled ? subOption.DescriptionLocalizations : null
));
}
}
}
minimalSourceOptions.Add(new DiscordApplicationCommandOption(
option.Name, option.Description, option.Type,
options: minimalSubSourceOptions,
nameLocalizations: localizationEnabled ? option.NameLocalizations : null,
descriptionLocalizations: localizationEnabled ? option.DescriptionLocalizations : null
));
}
foreach (var option in target.Options)
{
List<DiscordApplicationCommandOption> minimalSubTargetOptions = new();
foreach (var subOption in option.Options)
{
List<DiscordApplicationCommandOption> minimalSubSubTargetOptions = null;
if (subOption.Options != null && subOption.Options.Any())
{
minimalSubSubTargetOptions = new();
foreach (var subSubOption in subOption.Options)
minimalSubSubTargetOptions.Add(new DiscordApplicationCommandOption(
subSubOption.Name, subSubOption.Description, subSubOption.Type, subSubOption.Required,
subSubOption.Choices, null, subSubOption.ChannelTypes, subSubOption.AutoComplete,
subSubOption.MinimumValue, subSubOption.MaximumValue,
localizationEnabled ? subSubOption.NameLocalizations : null,
localizationEnabled ? subSubOption.DescriptionLocalizations : null,
subSubOption.MinimumLength, subSubOption.MaximumLength
));
minimalSubTargetOptions.Add(new DiscordApplicationCommandOption(
subOption.Name, subOption.Description, subOption.Type,
options: minimalSubSubTargetOptions,
nameLocalizations: localizationEnabled ? subOption.NameLocalizations : null,
descriptionLocalizations: localizationEnabled ? subOption.DescriptionLocalizations : null
));
}
}
minimalTargetOptions.Add(new DiscordApplicationCommandOption(
option.Name, option.Description, option.Type,
options: minimalSubTargetOptions,
nameLocalizations: localizationEnabled ? option.NameLocalizations : null,
descriptionLocalizations: localizationEnabled ? option.DescriptionLocalizations : null
));
}
var sOpt = JsonConvert.SerializeObject(minimalSourceOptions, Formatting.None);
var tOpt = JsonConvert.SerializeObject(minimalTargetOptions, Formatting.None);
//Console.WriteLine("Checking equality subcommandgroup");
//Console.WriteLine($"{rootCheck}");
//Console.WriteLine($"{sOpt}");
//Console.WriteLine($"{tOpt}");
return rootCheck && sOpt == tOpt;
}
else if (source.Options.Any(o => o.Type == ApplicationCommandOptionType.SubCommand) && target.Options.Any(o => o.Type == ApplicationCommandOptionType.SubCommand))
{
List<DiscordApplicationCommandOption> minimalSourceOptions = new();
List<DiscordApplicationCommandOption> minimalTargetOptions = new();
foreach (var option in source.Options)
{
List<DiscordApplicationCommandOption> minimalSubSourceOptions =null;
if (option.Options != null)
{
minimalSubSourceOptions = new();
foreach (var subOption in option.Options)
minimalSubSourceOptions.Add(new DiscordApplicationCommandOption(
subOption.Name, subOption.Description, subOption.Type, subOption.Required,
subOption.Choices, null, subOption.ChannelTypes, subOption.AutoComplete,
subOption.MinimumValue, subOption.MaximumValue,
localizationEnabled ? subOption.NameLocalizations : null,
localizationEnabled ? subOption.DescriptionLocalizations : null,
subOption.MinimumLength, subOption.MaximumLength
));
}
minimalSourceOptions.Add(new DiscordApplicationCommandOption(
option.Name, option.Description, option.Type,
options: minimalSubSourceOptions,
nameLocalizations: localizationEnabled ? option.NameLocalizations : null,
descriptionLocalizations: localizationEnabled ? option.DescriptionLocalizations : null
));
}
foreach (var option in target.Options)
{
List<DiscordApplicationCommandOption> minimalSubTargetOptions = null;
if (option.Options != null && option.Options.Any())
{
minimalSubTargetOptions = new();
foreach (var subOption in option.Options)
minimalSubTargetOptions.Add(new DiscordApplicationCommandOption(
subOption.Name, subOption.Description, subOption.Type, subOption.Required,
subOption.Choices, null, subOption.ChannelTypes, subOption.AutoComplete,
subOption.MinimumValue, subOption.MaximumValue,
localizationEnabled ? subOption.NameLocalizations : null,
localizationEnabled ? subOption.DescriptionLocalizations : null,
subOption.MinimumLength, subOption.MaximumLength
));
}
minimalTargetOptions.Add(new DiscordApplicationCommandOption(
option.Name, option.Description, option.Type,
options: minimalSubTargetOptions,
nameLocalizations: localizationEnabled ? option.NameLocalizations : null,
descriptionLocalizations: localizationEnabled ? option.DescriptionLocalizations : null
));
}
var sOpt = JsonConvert.SerializeObject(minimalSourceOptions, Formatting.None);
var tOpt = JsonConvert.SerializeObject(minimalTargetOptions, Formatting.None);
//Console.WriteLine("Checking equality subcommand");
//Console.WriteLine($"{rootCheck}");
//Console.WriteLine($"{sOpt}");
//Console.WriteLine($"{tOpt}");
return rootCheck && sOpt == tOpt;
}
else
{
List<DiscordApplicationCommandOption> minimalSourceOptions = new();
List<DiscordApplicationCommandOption> minimalTargetOptions = new();
foreach (var option in source.Options)
minimalSourceOptions.Add(new DiscordApplicationCommandOption(
option.Name, option.Description, option.Type, option.Required,
option.Choices, null, option.ChannelTypes, option.AutoComplete, option.MinimumValue, option.MaximumValue,
localizationEnabled ? option.NameLocalizations : null,
localizationEnabled ? option.DescriptionLocalizations : null,
option.MinimumLength, option.MaximumLength
));
foreach (var option in target.Options)
minimalTargetOptions.Add(new DiscordApplicationCommandOption(
option.Name, option.Description, option.Type, option.Required,
option.Choices, null, option.ChannelTypes, option.AutoComplete, option.MinimumValue, option.MaximumValue,
localizationEnabled ? option.NameLocalizations : null,
localizationEnabled ? option.DescriptionLocalizations : null,
option.MinimumLength, option.MaximumLength
));
var sOpt = JsonConvert.SerializeObject(minimalSourceOptions, Formatting.None);
var tOpt = JsonConvert.SerializeObject(minimalTargetOptions, Formatting.None);
//Console.WriteLine("Checking equality other");
//Console.WriteLine($"{rootCheck}");
//Console.WriteLine($"{sOpt}");
//Console.WriteLine($"{tOpt}");
return rootCheck && sOpt == tOpt;
}
}
}
diff --git a/DisCatSharp.ApplicationCommands/Context/ApplicationCommandsTranslationContext.cs b/DisCatSharp.ApplicationCommands/Context/ApplicationCommandsTranslationContext.cs
index e5574122e..fcf9a1837 100644
--- a/DisCatSharp.ApplicationCommands/Context/ApplicationCommandsTranslationContext.cs
+++ b/DisCatSharp.ApplicationCommands/Context/ApplicationCommandsTranslationContext.cs
@@ -1,68 +1,68 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Context;
/// <summary>
/// The application commands translation context.
/// </summary>
public class ApplicationCommandsTranslationContext
{
/// <summary>
/// Gets the type.
/// </summary>
public Type Type { get; }
/// <summary>
/// Gets the name.
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the group translation json.
/// </summary>
internal string GroupTranslations { get; set; }
/// <summary>
/// Gets the single translation json.
/// </summary>
internal string SingleTranslations { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="ApplicationCommandsTranslationContext"/> class.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="name">The name.</param>
internal ApplicationCommandsTranslationContext(Type type, string name)
{
this.Type = type;
this.Name = name;
}
public void AddGroupTranslation(string translationJson)
=> this.GroupTranslations = translationJson;
public void AddSingleTranslation(string translationJson)
=> this.SingleTranslations = translationJson;
}
diff --git a/DisCatSharp.ApplicationCommands/Context/AutocompleteContext.cs b/DisCatSharp.ApplicationCommands/Context/AutocompleteContext.cs
index 7a118a3d1..c72d55b86 100644
--- a/DisCatSharp.ApplicationCommands/Context/AutocompleteContext.cs
+++ b/DisCatSharp.ApplicationCommands/Context/AutocompleteContext.cs
@@ -1,105 +1,105 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using DisCatSharp.Entities;
using DisCatSharp.Enums;
using Microsoft.Extensions.DependencyInjection;
namespace DisCatSharp.ApplicationCommands.Context;
/// <summary>
/// Represents a context for an autocomplete interaction.
/// </summary>
public class AutocompleteContext
{
/// <summary>
/// The interaction created.
/// </summary>
public DiscordInteraction Interaction { get; internal set; }
/// <summary>
/// Gets the client for this interaction.
/// </summary>
public DiscordClient Client { get; internal set; }
/// <summary>
/// Gets the guild this interaction was executed in.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the channel this interaction was executed in.
/// </summary>
public DiscordChannel Channel { get; internal set; }
/// <summary>
/// Gets the user which executed this interaction.
/// </summary>
public DiscordUser User { get; internal set; }
/// <summary>
/// Gets the member which executed this interaction, or null if the command is in a DM.
/// </summary>
public DiscordMember Member
=> this.User is DiscordMember member ? member : null;
/// <summary>
/// Gets the invoking user locale.
/// </summary>
public string Locale { get; internal set; }
/// <summary>
/// Gets the guild locale if applicable.
/// </summary>
public string GuildLocale { get; internal set; }
/// <summary>
/// Gets the applications permissions.
/// </summary>
public Permissions AppPermissions { get; internal set; }
/// <summary>
/// Gets the slash command module this interaction was created in.
/// </summary>
public ApplicationCommandsExtension ApplicationCommandsExtension { get; internal set; }
/// <summary>
/// <para>Gets the service provider.</para>
/// <para>This allows passing data around without resorting to static members.</para>
/// <para>Defaults to an empty service provider.</para>
/// </summary>
public IServiceProvider Services { get; internal set; } = new ServiceCollection().BuildServiceProvider(true);
/// <summary>
/// The options already provided.
/// </summary>
public IReadOnlyList<DiscordInteractionDataOption> Options { get; internal set; }
/// <summary>
/// The option to autocomplete.
/// </summary>
public DiscordInteractionDataOption FocusedOption { get; internal set; }
}
diff --git a/DisCatSharp.ApplicationCommands/Context/BaseContext.cs b/DisCatSharp.ApplicationCommands/Context/BaseContext.cs
index bc30212c8..65110acc0 100644
--- a/DisCatSharp.ApplicationCommands/Context/BaseContext.cs
+++ b/DisCatSharp.ApplicationCommands/Context/BaseContext.cs
@@ -1,203 +1,203 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Threading.Tasks;
using DisCatSharp.Entities;
using DisCatSharp.Enums;
using Microsoft.Extensions.DependencyInjection;
namespace DisCatSharp.ApplicationCommands.Context;
/// <summary>
/// Represents a base context for application command contexts.
/// </summary>
public class BaseContext
{
/// <summary>
/// Gets the interaction that was created.
/// </summary>
public DiscordInteraction Interaction { get; internal set; }
/// <summary>
/// Gets the client for this interaction.
/// </summary>
public DiscordClient Client { get; internal set; }
/// <summary>
/// Gets the guild this interaction was executed in.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the channel this interaction was executed in.
/// </summary>
public DiscordChannel Channel { get; internal set; }
/// <summary>
/// Gets the user which executed this interaction.
/// </summary>
public DiscordUser User { get; internal set; }
/// <summary>
/// Gets the member which executed this interaction, or null if the command is in a DM.
/// </summary>
public DiscordMember Member
=> this.User is DiscordMember member ? member : null;
/// <summary>
/// Gets the application command module this interaction was created in.
/// </summary>
public ApplicationCommandsExtension ApplicationCommandsExtension { get; internal set; }
/// <summary>
/// Gets the token for this interaction.
/// </summary>
public string Token { get; internal set; }
/// <summary>
/// Gets the id for this interaction.
/// </summary>
public ulong InteractionId { get; internal set; }
/// <summary>
/// Gets the name of the command.
/// </summary>
public string CommandName { get; internal set; }
/// <summary>
/// Gets the invoking user locale.
/// </summary>
public string Locale { get; internal set; }
/// <summary>
/// Gets the guild locale if applicable.
/// </summary>
public string GuildLocale { get; internal set; }
/// <summary>
/// Gets the applications permissions.
/// </summary>
public Permissions AppPermissions { get; internal set; }
/// <summary>
/// Gets the type of this interaction.
/// </summary>
public ApplicationCommandType Type { get; internal set; }
/// <summary>
/// <para>Gets the service provider.</para>
/// <para>This allows passing data around without resorting to static members.</para>
/// <para>Defaults to an empty service provider.</para>
/// </summary>
public IServiceProvider Services { get; internal set; } = new ServiceCollection().BuildServiceProvider(true);
/// <summary>
/// Creates a response to this interaction.
/// <para>You must create a response within 3 seconds of this interaction being executed; if the command has the potential to take more than 3 seconds, create a <see cref="InteractionResponseType.DeferredChannelMessageWithSource"/> at the start, and edit the response later.</para>
/// </summary>
/// <param name="type">The type of the response.</param>
/// <param name="builder">The data to be sent, if any.</param>
/// <returns></returns>
public Task CreateResponseAsync(InteractionResponseType type, DiscordInteractionResponseBuilder builder = null)
=> this.Interaction.CreateResponseAsync(type, builder);
/// <summary>
/// Creates a modal response to this interaction.
/// </summary>
/// <param name="builder">The data to send.</param>
public Task CreateModalResponseAsync(DiscordInteractionModalBuilder builder)
=> this.Interaction.Type != InteractionType.Ping && this.Interaction.Type != InteractionType.ModalSubmit ? this.Interaction.CreateInteractionModalResponseAsync(builder) : throw new NotSupportedException("You can't respond to an PING with a modal.");
/// <summary>
/// Edits the interaction response.
/// </summary>
/// <param name="builder">The data to edit the response with.</param>
/// <returns></returns>
public Task<DiscordMessage> EditResponseAsync(DiscordWebhookBuilder builder)
=> this.Interaction.EditOriginalResponseAsync(builder);
/// <summary>
/// Deletes the interaction response.
/// </summary>
/// <returns></returns>
public Task DeleteResponseAsync()
=> this.Interaction.DeleteOriginalResponseAsync();
/// <summary>
/// Creates a follow up message to the interaction.
/// </summary>
/// <param name="builder">The message to be sent, in the form of a webhook.</param>
/// <returns>The created message.</returns>
public Task<DiscordMessage> FollowUpAsync(DiscordFollowupMessageBuilder builder)
=> this.Interaction.CreateFollowupMessageAsync(builder);
/// <summary>
/// Creates a follow up message to the interaction.
/// </summary>
/// <param name="content">The content of the message to be sent.</param>
/// <returns>The created message.</returns>
public Task<DiscordMessage> FollowUpAsync(string content)
=> this.FollowUpAsync(new DiscordFollowupMessageBuilder().WithContent(content));
/// <summary>
/// Edits a followup message.
/// </summary>
/// <param name="followupMessageId">The id of the followup message to edit.</param>
/// <param name="builder">The webhook builder.</param>
/// <returns>The created message.</returns>
public Task<DiscordMessage> EditFollowupAsync(ulong followupMessageId, DiscordWebhookBuilder builder)
=> this.Interaction.EditFollowupMessageAsync(followupMessageId, builder);
/// <summary>
/// Edits a followup message.
/// </summary>
/// <param name="followupMessageId">The id of the followup message to edit.</param>
/// <param name="content">The content of the webhook.</param>
/// <returns>The created message.</returns>
public Task<DiscordMessage> EditFollowupAsync(ulong followupMessageId, string content)
=> this.EditFollowupAsync(followupMessageId, new DiscordWebhookBuilder().WithContent(content));
/// <summary>
/// Deletes a followup message.
/// </summary>
/// <param name="followupMessageId">The id of the followup message to delete.</param>
/// <returns></returns>
public Task DeleteFollowupAsync(ulong followupMessageId)
=> this.Interaction.DeleteFollowupMessageAsync(followupMessageId);
/// <summary>
/// Gets the followup message.
/// </summary>
/// <param name="followupMessageId">The followup message id.</param>
public Task<DiscordMessage> GetFollowupMessageAsync(ulong followupMessageId)
=> this.Interaction.GetFollowupMessageAsync(followupMessageId);
/// <summary>
/// Gets the original interaction response.
/// </summary>
/// <returns>The original interaction response.</returns>
public Task<DiscordMessage> GetOriginalResponseAsync()
=> this.Interaction.GetOriginalResponseAsync();
}
diff --git a/DisCatSharp.ApplicationCommands/Context/ContextMenuContext.cs b/DisCatSharp.ApplicationCommands/Context/ContextMenuContext.cs
index 932f40723..b8dcfade7 100644
--- a/DisCatSharp.ApplicationCommands/Context/ContextMenuContext.cs
+++ b/DisCatSharp.ApplicationCommands/Context/ContextMenuContext.cs
@@ -1,47 +1,47 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
namespace DisCatSharp.ApplicationCommands.Context;
/// <summary>
/// Represents a context for a context menu.
/// </summary>
public sealed class ContextMenuContext : BaseContext
{
/// <summary>
/// The user this command targets, if applicable.
/// </summary>
public DiscordUser TargetUser { get; internal set; }
/// <summary>
/// The member this command targets, if applicable.
/// </summary>
public DiscordMember TargetMember
=> this.TargetUser is DiscordMember member ? member : null;
/// <summary>
/// The message this command targets, if applicable.
/// </summary>
public DiscordMessage TargetMessage { get; internal set; }
}
diff --git a/DisCatSharp.ApplicationCommands/Context/InteractionContext.cs b/DisCatSharp.ApplicationCommands/Context/InteractionContext.cs
index 19d8d3483..b14671f41 100644
--- a/DisCatSharp.ApplicationCommands/Context/InteractionContext.cs
+++ b/DisCatSharp.ApplicationCommands/Context/InteractionContext.cs
@@ -1,53 +1,53 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Context;
/// <summary>
/// Represents a context for an interaction.
/// </summary>
public sealed class InteractionContext : BaseContext
{
/// <summary>
/// Gets the users mentioned in the command parameters.
/// </summary>
public IReadOnlyList<DiscordUser> ResolvedUserMentions { get; internal set; }
/// <summary>
/// Gets the roles mentioned in the command parameters.
/// </summary>
public IReadOnlyList<DiscordRole> ResolvedRoleMentions { get; internal set; }
/// <summary>
/// Gets the channels mentioned in the command parameters.
/// </summary>
public IReadOnlyList<DiscordChannel> ResolvedChannelMentions { get; internal set; }
/// <summary>
/// Gets the attachments in the command parameters, if applicable.
/// </summary>
public IReadOnlyList<DiscordAttachment> ResolvedAttachments { get; internal set; }
}
diff --git a/DisCatSharp.ApplicationCommands/Entities/ChoiceTranslator.cs b/DisCatSharp.ApplicationCommands/Entities/ChoiceTranslator.cs
index 294ed0e86..b4270f30c 100644
--- a/DisCatSharp.ApplicationCommands/Entities/ChoiceTranslator.cs
+++ b/DisCatSharp.ApplicationCommands/Entities/ChoiceTranslator.cs
@@ -1,50 +1,50 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Entities;
/// <summary>
/// Represents a choice translator.
/// </summary>
internal class ChoiceTranslator
{
/// <summary>
/// Gets the choice name.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Gets the choice name translations.
/// </summary>
[JsonProperty("name_translations")]
internal Dictionary<string, string> NameTranslationsDictionary { get; set; }
[JsonIgnore]
public DiscordApplicationCommandLocalization NameTranslations
=> new(this.NameTranslationsDictionary);
}
diff --git a/DisCatSharp.ApplicationCommands/Entities/CommandTranslator.cs b/DisCatSharp.ApplicationCommands/Entities/CommandTranslator.cs
index f72cfc7f6..89d36f711 100644
--- a/DisCatSharp.ApplicationCommands/Entities/CommandTranslator.cs
+++ b/DisCatSharp.ApplicationCommands/Entities/CommandTranslator.cs
@@ -1,79 +1,79 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Entities;
/// <summary>
/// Represents a command translator.
/// </summary>
internal class CommandTranslator
{
/// <summary>
/// Gets the command name.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Gets the command description.
/// </summary>
[JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)]
public string? Description { get; set; }
/// <summary>
/// Gets the application command type.
/// Used to determine whether it is an translator for context menu or not.
/// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
public ApplicationCommandType? Type { get; set; }
/// <summary>
/// Gets the command name translations.
/// </summary>
[JsonProperty("name_translations")]
internal Dictionary<string, string> NameTranslationDictionary { get; set; }
[JsonIgnore]
public DiscordApplicationCommandLocalization NameTranslations
=> new(this.NameTranslationDictionary);
/// <summary>
/// Gets the command description translations.
/// </summary>
[JsonProperty("description_translations")]
internal Dictionary<string, string> DescriptionTranslationDictionary { get; set; }
[JsonIgnore]
public DiscordApplicationCommandLocalization DescriptionTranslations
=> new(this.DescriptionTranslationDictionary);
/// <summary>
/// Gets the option translators, if applicable.
/// </summary>
[JsonProperty("options", NullValueHandling = NullValueHandling.Ignore)]
public List<OptionTranslator> Options { get; set; }
}
diff --git a/DisCatSharp.ApplicationCommands/Entities/CooldownBucket.cs b/DisCatSharp.ApplicationCommands/Entities/CooldownBucket.cs
index b43cef26e..2c09f4be5 100644
--- a/DisCatSharp.ApplicationCommands/Entities/CooldownBucket.cs
+++ b/DisCatSharp.ApplicationCommands/Entities/CooldownBucket.cs
@@ -1,186 +1,186 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Threading;
using System.Threading.Tasks;
namespace DisCatSharp.ApplicationCommands.Entities;
public class CooldownBucket : IBucket, IEquatable<CooldownBucket>
{
/// <summary>
/// The user id for this bucket.
/// </summary>
public ulong UserId { get; }
/// <summary>
/// The channel id for this bucket.
/// </summary>
public ulong ChannelId { get; }
/// <summary>
/// The guild id for this bucket.
/// </summary>
public ulong GuildId { get; }
/// <summary>
/// The id for this bucket.
/// </summary>
public string BucketId { get; }
/// <summary>
/// The remaining uses for this bucket.
/// </summary>
public int RemainingUses => Volatile.Read(ref this._remainingUses);
/// <summary>
/// The max uses for this bucket.
/// </summary>
public int MaxUses { get; }
/// <summary>
/// The datetime offset when this bucket resets.
/// </summary>
public DateTimeOffset ResetsAt { get; internal set; }
/// <summary>
/// The timespan when this bucket resets.
/// </summary>
public TimeSpan Reset { get; internal set; }
/// <summary>
/// Gets the semaphore used to lock the use value.
/// </summary>
internal readonly SemaphoreSlim _usageSemaphore;
internal int _remainingUses;
/// <summary>
/// Creates a new command cooldown bucket.
/// </summary>
/// <param name="maxUses">Maximum number of uses for this bucket.</param>
/// <param name="resetAfter">Time after which this bucket resets.</param>
/// <param name="userId">ID of the user with which this cooldown is associated.</param>
/// <param name="channelId">ID of the channel with which this cooldown is associated.</param>
/// <param name="guildId">ID of the guild with which this cooldown is associated.</param>
internal CooldownBucket(int maxUses, TimeSpan resetAfter, ulong userId = 0, ulong channelId = 0, ulong guildId = 0)
{
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(1, 1);
}
/// <summary>
/// Decrements the remaining use counter.
/// </summary>
/// <returns>Whether decrement succeeded or not.</returns>
internal async Task<bool> DecrementUseAsync()
{
await this._usageSemaphore.WaitAsync().ConfigureAwait(false);
Console.WriteLine($"[DecrementUseAsync]: Remaining: {this.RemainingUses}/{this.MaxUses} Resets: {this.ResetsAt} Now: {DateTimeOffset.UtcNow} Vars[u,c,g]: {this.UserId} {this.ChannelId} {this.GuildId} Id: {this.BucketId}");
// 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._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._remainingUses);
success = true;
}
Console.WriteLine($"[DecrementUseAsync]: Remaining: {this.RemainingUses}/{this.MaxUses} Resets: {this.ResetsAt} Now: {DateTimeOffset.UtcNow} Vars[u,c,g]: {this.UserId} {this.ChannelId} {this.GuildId} Id: {this.BucketId}");
// ...otherwise just fail
this._usageSemaphore.Release();
return success;
}
/// <summary>
/// Checks whether this <see cref="CooldownBucket"/> is equal to another object.
/// </summary>
/// <param name="obj">Object to compare to.</param>
/// <returns>Whether the object is equal to this <see cref="CooldownBucket"/>.</returns>
public override bool Equals(object obj) => this.Equals(obj as CooldownBucket);
/// <summary>
/// Checks whether this <see cref="CooldownBucket"/> is equal to another <see cref="CooldownBucket"/>.
/// </summary>
/// <param name="other"><see cref="CooldownBucket"/> to compare to.</param>
/// <returns>Whether the <see cref="CooldownBucket"/> is equal to this <see cref="CooldownBucket"/>.</returns>
public bool Equals(CooldownBucket other) => other is not null && (ReferenceEquals(this, other) || (this.UserId == other.UserId && this.ChannelId == other.ChannelId && this.GuildId == other.GuildId));
/// <summary>
/// Gets the hash code for this <see cref="CooldownBucket"/>.
/// </summary>
/// <returns>The hash code for this <see cref="CooldownBucket"/>.</returns>
public override int GetHashCode() => HashCode.Combine(this.UserId, this.ChannelId, this.GuildId);
/// <summary>
/// Gets whether the two <see cref="CooldownBucket"/> objects are equal.
/// </summary>
/// <param name="bucket1">First bucket to compare.</param>
/// <param name="bucket2">Second bucket to compare.</param>
/// <returns>Whether the two buckets are equal.</returns>
public static bool operator ==(CooldownBucket bucket1, CooldownBucket bucket2)
{
var null1 = bucket1 is null;
var null2 = bucket2 is null;
return (null1 && null2) || (null1 == null2 && null1.Equals(null2));
}
/// <summary>
/// Gets whether the two <see cref="CooldownBucket"/> objects are not equal.
/// </summary>
/// <param name="bucket1">First bucket to compare.</param>
/// <param name="bucket2">Second bucket to compare.</param>
/// <returns>Whether the two buckets are not equal.</returns>
public static bool operator !=(CooldownBucket bucket1, CooldownBucket bucket2)
=> !(bucket1 == bucket2);
/// <summary>
/// Creates a bucket ID from given bucket parameters.
/// </summary>
/// <param name="userId">ID of the user with which this cooldown is associated.</param>
/// <param name="channelId">ID of the channel with which this cooldown is associated.</param>
/// <param name="guildId">ID of the guild with which this cooldown is associated.</param>
/// <returns>Generated bucket ID.</returns>
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.ApplicationCommands/Entities/FakeApplicationCommandObjects.cs b/DisCatSharp.ApplicationCommands/Entities/FakeApplicationCommandObjects.cs
index 299b8cb9e..1b26627f0 100644
--- a/DisCatSharp.ApplicationCommands/Entities/FakeApplicationCommandObjects.cs
+++ b/DisCatSharp.ApplicationCommands/Entities/FakeApplicationCommandObjects.cs
@@ -1,85 +1,85 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Entities;
internal class CommandGroupWithSubGroups : BaseCommand
{
[JsonProperty("groups")]
internal List<CommandGroup> SubGroups { get; set; }
internal CommandGroupWithSubGroups(string name, string description, List<CommandGroup> commands, ApplicationCommandType type)
: base(name, description, type)
{
this.SubGroups = commands;
}
}
internal class CommandGroup : BaseCommand
{
[JsonProperty("commands")]
internal List<Command> Commands { get; set; }
internal CommandGroup(string name, string description, List<Command> commands, ApplicationCommandType? type = null)
: base(name, description, type)
{
this.Commands = commands;
}
}
internal class Command : BaseCommand
{
[JsonProperty("options")]
internal List<DiscordApplicationCommandOption> Options { get; set; }
internal Command(string name, string? description = null, List<DiscordApplicationCommandOption> options = null, ApplicationCommandType? type = null)
: base(name, description, type)
{
this.Options = options;
}
}
internal class BaseCommand
{
[JsonProperty("name")]
internal string Name { get; set; }
[JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)]
internal string? Description { get; set; }
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
internal ApplicationCommandType? Type { get; set; }
internal BaseCommand(string name, string? description = null, ApplicationCommandType? type = null)
{
this.Name = name;
this.Type = type;
this.Description = description;
}
}
diff --git a/DisCatSharp.ApplicationCommands/Entities/GroupTranslator.cs b/DisCatSharp.ApplicationCommands/Entities/GroupTranslator.cs
index 8c361699b..6cdf393be 100644
--- a/DisCatSharp.ApplicationCommands/Entities/GroupTranslator.cs
+++ b/DisCatSharp.ApplicationCommands/Entities/GroupTranslator.cs
@@ -1,85 +1,85 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Entities;
/// <summary>
/// Represents a group translator.
/// </summary>
internal class GroupTranslator
{
/// <summary>
/// Gets the group name.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Gets the group description.
/// </summary>
[JsonProperty("description")]
public string Description { get; set; }
/// <summary>
/// Gets the application command type.
/// Used to determine whether it is an translator for context menu or not.
/// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
public ApplicationCommandType? Type { get; set; }
/// <summary>
/// Gets the group name translations.
/// </summary>
[JsonProperty("name_translations")]
internal Dictionary<string, string> NameTranslationsDictionary { get; set; }
[JsonIgnore]
public DiscordApplicationCommandLocalization NameTranslations
=> new(this.NameTranslationsDictionary);
/// <summary>
/// Gets the group description translations.
/// </summary>
[JsonProperty("description_translations")]
internal Dictionary<string, string> DescriptionTranslationsDictionary { get; set; }
[JsonIgnore]
public DiscordApplicationCommandLocalization DescriptionTranslations
=> new(this.DescriptionTranslationsDictionary);
/// <summary>
/// Gets the sub group translators, if applicable.
/// </summary>
[JsonProperty("groups", NullValueHandling = NullValueHandling.Ignore)]
public List<SubGroupTranslator> SubGroups { get; set; }
/// <summary>
/// Gets the command translators, if applicable.
/// </summary>
[JsonProperty("commands", NullValueHandling = NullValueHandling.Ignore)]
public List<CommandTranslator> Commands { get; set; }
}
diff --git a/DisCatSharp.ApplicationCommands/Entities/IBucket.cs b/DisCatSharp.ApplicationCommands/Entities/IBucket.cs
index 5ef9aee60..81ecf72d4 100644
--- a/DisCatSharp.ApplicationCommands/Entities/IBucket.cs
+++ b/DisCatSharp.ApplicationCommands/Entities/IBucket.cs
@@ -1,71 +1,71 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Entities;
/// <summary>
/// Defines the standard contract for bucket feature
/// </summary>
public interface IBucket
{
/// <summary>
/// Gets the ID of the user whom this cooldown is associated
/// </summary>
ulong UserId { get; }
/// <summary>
/// Gets the ID of the channel with which this cooldown is associated
/// </summary>
ulong ChannelId { get; }
/// <summary>
/// Gets the ID of the guild with which this cooldown is associated
/// </summary>
ulong GuildId { get; }
/// <summary>
/// Gets the ID of the bucket. This is used to distinguish between cooldown buckets
/// </summary>
string BucketId { get; }
/// <summary>
/// Gets the remaining number of uses before the cooldown is triggered
/// </summary>
int RemainingUses { get; }
/// <summary>
/// Gets the maximum number of times this command can be used in a given timespan
/// </summary>
int MaxUses { get; }
/// <summary>
/// Gets the date and time at which the cooldown resets
/// </summary>
DateTimeOffset ResetsAt { get; }
/// <summary>
/// Get the time after which this cooldown resets
/// </summary>
TimeSpan Reset { get; }
}
diff --git a/DisCatSharp.ApplicationCommands/Entities/ICooldown.cs b/DisCatSharp.ApplicationCommands/Entities/ICooldown.cs
index 17097417d..4e484e6f3 100644
--- a/DisCatSharp.ApplicationCommands/Entities/ICooldown.cs
+++ b/DisCatSharp.ApplicationCommands/Entities/ICooldown.cs
@@ -1,67 +1,67 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.ApplicationCommands.Context;
using DisCatSharp.ApplicationCommands.Enums;
namespace DisCatSharp.ApplicationCommands.Entities;
/// <summary>
/// Cooldown feature contract
/// </summary>
/// <typeparam name="TContextType">Type of <see cref="BaseContext"/> in which this cooldown handles</typeparam>
/// <typeparam name="TBucketType">Type of Cooldown bucket</typeparam>
public interface ICooldown<in TContextType, out TBucketType>
where TContextType : BaseContext
where TBucketType : CooldownBucket
{
/// <summary>
/// Gets the maximum number of uses before this command triggers a cooldown for its bucket.
/// </summary>
int MaxUses { get; }
/// <summary>
/// Gets the time after which the cooldown is reset.
/// </summary>
TimeSpan Reset { get; }
/// <summary>
/// Gets the type of the cooldown bucket. This determines how a cooldown is applied.
/// </summary>
CooldownBucketType BucketType { get; }
/// <summary>
/// Calculates the cooldown remaining for given context.
/// </summary>
/// <param name="ctx">Context for which to calculate the cooldown.</param>
/// <returns>Remaining cooldown, or zero if no cooldown is active</returns>
TimeSpan GetRemainingCooldown(TContextType ctx);
/// <summary>
/// Gets a cooldown bucket for given context
/// </summary>
/// <param name="ctx">Command context to get cooldown bucket for.</param>
/// <returns>Requested cooldown bucket, or null if one wasn't present</returns>
TBucketType GetBucket(TContextType ctx);
}
diff --git a/DisCatSharp.ApplicationCommands/Entities/OptionTranslator.cs b/DisCatSharp.ApplicationCommands/Entities/OptionTranslator.cs
index d8692d2d2..9f728829d 100644
--- a/DisCatSharp.ApplicationCommands/Entities/OptionTranslator.cs
+++ b/DisCatSharp.ApplicationCommands/Entities/OptionTranslator.cs
@@ -1,71 +1,71 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Entities;
/// <summary>
/// Represents a option translator.
/// </summary>
internal class OptionTranslator
{
/// <summary>
/// Gets the option name.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Gets the option description.
/// </summary>
[JsonProperty("description")]
public string Description { get; set; }
/// <summary>
/// Gets the option name translations.
/// </summary>
[JsonProperty("name_translations")]
internal Dictionary<string, string> NameTranslationsDictionary { get; set; }
[JsonIgnore]
public DiscordApplicationCommandLocalization NameTranslations
=> new(this.NameTranslationsDictionary);
/// <summary>
/// Gets the option description translations.
/// </summary>
[JsonProperty("description_translations")]
internal Dictionary<string, string> DescriptionTranslationsDictionary { get; set; }
[JsonIgnore]
public DiscordApplicationCommandLocalization DescriptionTranslations
=> new(this.DescriptionTranslationsDictionary);
/// <summary>
/// Gets the choice translators, if applicable.
/// </summary>
[JsonProperty("choices", NullValueHandling = NullValueHandling.Ignore)]
public List<ChoiceTranslator> Choices { get; set; }
}
diff --git a/DisCatSharp.ApplicationCommands/Entities/SubGroupTranslator.cs b/DisCatSharp.ApplicationCommands/Entities/SubGroupTranslator.cs
index 41b4f3f8c..d89f61239 100644
--- a/DisCatSharp.ApplicationCommands/Entities/SubGroupTranslator.cs
+++ b/DisCatSharp.ApplicationCommands/Entities/SubGroupTranslator.cs
@@ -1,71 +1,71 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Entities;
/// <summary>
/// Represents a sub group translator.
/// </summary>
internal class SubGroupTranslator
{
/// <summary>
/// Gets the sub group name.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Gets the sub group description.
/// </summary>
[JsonProperty("description")]
public string Description { get; set; }
/// <summary>
/// Gets the sub group name translations.
/// </summary>
[JsonProperty("name_translations")]
internal Dictionary<string, string> NameTranslationsDictionary { get; set; }
[JsonIgnore]
public DiscordApplicationCommandLocalization NameTranslations
=> new(this.NameTranslationsDictionary);
/// <summary>
/// Gets the sub group description translations.
/// </summary>
[JsonProperty("description_translations")]
internal Dictionary<string, string> DescriptionTranslationsDictionary { get; set; }
[JsonIgnore]
public DiscordApplicationCommandLocalization DescriptionTranslations
=> new(this.DescriptionTranslationsDictionary);
/// <summary>
/// Gets the command translators.
/// </summary>
[JsonProperty("commands", NullValueHandling = NullValueHandling.Ignore)]
public List<CommandTranslator> Commands { get; set; }
}
diff --git a/DisCatSharp.ApplicationCommands/Enums/ApplicationCommandModuleLifespan.cs b/DisCatSharp.ApplicationCommands/Enums/ApplicationCommandModuleLifespan.cs
index c8b2ac181..9c6abb35e 100644
--- a/DisCatSharp.ApplicationCommands/Enums/ApplicationCommandModuleLifespan.cs
+++ b/DisCatSharp.ApplicationCommands/Enums/ApplicationCommandModuleLifespan.cs
@@ -1,44 +1,44 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.ApplicationCommands.Enums;
/// <summary>
/// Represents a application command module lifespan.
/// </summary>
public enum ApplicationCommandModuleLifespan
{
/// <summary>
/// Whether this module should be initiated every time a command is run, with dependencies injected from a scope.
/// </summary>
Scoped,
/// <summary>
/// Whether this module should be initiated every time a command is run.
/// </summary>
Transient,
/// <summary>
/// Whether this module should be initiated at startup.
/// </summary>
Singleton
}
diff --git a/DisCatSharp.ApplicationCommands/Enums/CooldownBucketType.cs b/DisCatSharp.ApplicationCommands/Enums/CooldownBucketType.cs
index 3b1330bc8..4a54e0b55 100644
--- a/DisCatSharp.ApplicationCommands/Enums/CooldownBucketType.cs
+++ b/DisCatSharp.ApplicationCommands/Enums/CooldownBucketType.cs
@@ -1,50 +1,50 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.ApplicationCommands.Enums;
/// <summary>
/// Defines how are command cooldowns applied.
/// </summary>
public enum CooldownBucketType : int
{
/// <summary>
/// Denotes that the command will have its cooldown applied globally.
/// </summary>
Global = 0,
/// <summary>
/// Denotes that the command will have its cooldown applied per-user.
/// </summary>
User = 1,
/// <summary>
/// Denotes that the command will have its cooldown applied per-channel.
/// </summary>
Channel = 2,
/// <summary>
/// Denotes that the command will have its cooldown applied per-guild. In DMs, this applies the cooldown per-channel.
/// </summary>
Guild = 4
}
diff --git a/DisCatSharp.ApplicationCommands/EventArgs/ContextMenu/ContextMenuErrorEventArgs.cs b/DisCatSharp.ApplicationCommands/EventArgs/ContextMenu/ContextMenuErrorEventArgs.cs
index 8c573f261..93bb64028 100644
--- a/DisCatSharp.ApplicationCommands/EventArgs/ContextMenu/ContextMenuErrorEventArgs.cs
+++ b/DisCatSharp.ApplicationCommands/EventArgs/ContextMenu/ContextMenuErrorEventArgs.cs
@@ -1,52 +1,52 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.ApplicationCommands.Context;
using DisCatSharp.EventArgs;
namespace DisCatSharp.ApplicationCommands.EventArgs;
/// <summary>
/// Represents arguments for a <see cref="ApplicationCommandsExtension.ContextMenuErrored"/>
/// </summary>
public class ContextMenuErrorEventArgs : DiscordEventArgs
{
/// <summary>
/// The context of the command.
/// </summary>
public ContextMenuContext Context { get; internal set; }
/// <summary>
/// The exception thrown.
/// </summary>
public Exception Exception { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="ContextMenuErrorEventArgs"/> class.
/// </summary>
/// <param name="provider">The provider.</param>
public ContextMenuErrorEventArgs(IServiceProvider provider) : base(provider)
{ }
}
diff --git a/DisCatSharp.ApplicationCommands/EventArgs/ContextMenu/ContextMenuExecutedEventArgs.cs b/DisCatSharp.ApplicationCommands/EventArgs/ContextMenu/ContextMenuExecutedEventArgs.cs
index b92eb2b68..c07cd8140 100644
--- a/DisCatSharp.ApplicationCommands/EventArgs/ContextMenu/ContextMenuExecutedEventArgs.cs
+++ b/DisCatSharp.ApplicationCommands/EventArgs/ContextMenu/ContextMenuExecutedEventArgs.cs
@@ -1,47 +1,47 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.ApplicationCommands.Context;
using DisCatSharp.EventArgs;
namespace DisCatSharp.ApplicationCommands.EventArgs;
/// <summary>
/// Represents the arguments for a <see cref="ApplicationCommandsExtension.ContextMenuExecuted"/> event
/// </summary>
public sealed class ContextMenuExecutedEventArgs : DiscordEventArgs
{
/// <summary>
/// The context of the command.
/// </summary>
public ContextMenuContext Context { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="ContextMenuExecutedEventArgs"/> class.
/// </summary>
/// <param name="provider">The provider.</param>
public ContextMenuExecutedEventArgs(IServiceProvider provider) : base(provider)
{ }
}
diff --git a/DisCatSharp.ApplicationCommands/EventArgs/Module/ApplicationCommandsModuleReadyEventArgs.cs b/DisCatSharp.ApplicationCommands/EventArgs/Module/ApplicationCommandsModuleReadyEventArgs.cs
index 5825aaf22..916345d72 100644
--- a/DisCatSharp.ApplicationCommands/EventArgs/Module/ApplicationCommandsModuleReadyEventArgs.cs
+++ b/DisCatSharp.ApplicationCommands/EventArgs/Module/ApplicationCommandsModuleReadyEventArgs.cs
@@ -1,46 +1,46 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using DisCatSharp.EventArgs;
namespace DisCatSharp.ApplicationCommands.EventArgs;
/// <summary>
/// Represents arguments for a <see cref="ApplicationCommandsExtension.ApplicationCommandsModuleReady"/> event.
/// </summary>
public class ApplicationCommandsModuleReadyEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets a list of all guild ids missing the application commands scope.
/// </summary>
public IReadOnlyList<ulong> GuildsWithoutScope { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="ApplicationCommandsModuleReadyEventArgs"/> class.
/// </summary>
/// <param name="provider">The provider.</param>
internal ApplicationCommandsModuleReadyEventArgs(IServiceProvider provider) : base(provider)
{ }
}
diff --git a/DisCatSharp.ApplicationCommands/EventArgs/Module/ApplicationCommandsModuleStartupFinishedEventArgs.cs b/DisCatSharp.ApplicationCommands/EventArgs/Module/ApplicationCommandsModuleStartupFinishedEventArgs.cs
index 9b6ceb90a..dd4fc477f 100644
--- a/DisCatSharp.ApplicationCommands/EventArgs/Module/ApplicationCommandsModuleStartupFinishedEventArgs.cs
+++ b/DisCatSharp.ApplicationCommands/EventArgs/Module/ApplicationCommandsModuleStartupFinishedEventArgs.cs
@@ -1,57 +1,57 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using DisCatSharp.Entities;
using DisCatSharp.EventArgs;
namespace DisCatSharp.ApplicationCommands.EventArgs;
/// <summary>
/// Represents arguments for a <see cref="ApplicationCommandsExtension.ApplicationCommandsModuleStartupFinished"/> event.
/// </summary>
public class ApplicationCommandsModuleStartupFinishedEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets a list of all guild ids missing the application commands scope.
/// </summary>
public IReadOnlyList<ulong> GuildsWithoutScope { get; internal set; }
/// <summary>
/// Gets all registered global commands.
/// </summary>
public IReadOnlyList<DiscordApplicationCommand> RegisteredGlobalCommands { get; internal set; }
/// <summary>
/// Gets all registered guild commands mapped by guild id.
/// </summary>
public IReadOnlyDictionary<ulong, IReadOnlyList<DiscordApplicationCommand>> RegisteredGuildCommands { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="ApplicationCommandsModuleStartupFinishedEventArgs"/> class.
/// </summary>
/// <param name="provider">The provider.</param>
internal ApplicationCommandsModuleStartupFinishedEventArgs(IServiceProvider provider) : base(provider)
{ }
}
diff --git a/DisCatSharp.ApplicationCommands/EventArgs/Module/GlobalApplicationCommandsRegisteredEventArgs.cs b/DisCatSharp.ApplicationCommands/EventArgs/Module/GlobalApplicationCommandsRegisteredEventArgs.cs
index 391051bae..6f9c6a940 100644
--- a/DisCatSharp.ApplicationCommands/EventArgs/Module/GlobalApplicationCommandsRegisteredEventArgs.cs
+++ b/DisCatSharp.ApplicationCommands/EventArgs/Module/GlobalApplicationCommandsRegisteredEventArgs.cs
@@ -1,47 +1,47 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using DisCatSharp.Entities;
using DisCatSharp.EventArgs;
namespace DisCatSharp.ApplicationCommands.EventArgs;
/// <summary>
/// Represents arguments for a <see cref="ApplicationCommandsExtension.GlobalApplicationCommandsRegistered"/> event.
/// </summary>
public class GlobalApplicationCommandsRegisteredEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets all registered global commands.
/// </summary>
public IReadOnlyList<DiscordApplicationCommand> RegisteredCommands { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GlobalApplicationCommandsRegisteredEventArgs"/> class.
/// </summary>
/// <param name="provider">The provider.</param>
internal GlobalApplicationCommandsRegisteredEventArgs(IServiceProvider provider) : base(provider)
{ }
}
diff --git a/DisCatSharp.ApplicationCommands/EventArgs/Module/GuildApplicationCommandsRegisteredEventArgs.cs b/DisCatSharp.ApplicationCommands/EventArgs/Module/GuildApplicationCommandsRegisteredEventArgs.cs
index 7eee4ec19..2f632df57 100644
--- a/DisCatSharp.ApplicationCommands/EventArgs/Module/GuildApplicationCommandsRegisteredEventArgs.cs
+++ b/DisCatSharp.ApplicationCommands/EventArgs/Module/GuildApplicationCommandsRegisteredEventArgs.cs
@@ -1,52 +1,52 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using DisCatSharp.Entities;
using DisCatSharp.EventArgs;
namespace DisCatSharp.ApplicationCommands.EventArgs;
/// <summary>
/// Represents arguments for a <see cref="ApplicationCommandsExtension.GuildApplicationCommandsRegistered"/> event.
/// </summary>
public class GuildApplicationCommandsRegisteredEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the target guild id.
/// </summary>
public ulong GuildId { get; internal set; }
/// <summary>
/// Gets all registered guild commands.
/// </summary>
public IReadOnlyList<DiscordApplicationCommand> RegisteredCommands { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildApplicationCommandsRegisteredEventArgs"/> class.
/// </summary>
/// <param name="provider">The provider.</param>
internal GuildApplicationCommandsRegisteredEventArgs(IServiceProvider provider) : base(provider)
{ }
}
diff --git a/DisCatSharp.ApplicationCommands/EventArgs/SlashCommand/SlashCommandErrorEventArgs.cs b/DisCatSharp.ApplicationCommands/EventArgs/SlashCommand/SlashCommandErrorEventArgs.cs
index 6fa0a61cd..d41335a5e 100644
--- a/DisCatSharp.ApplicationCommands/EventArgs/SlashCommand/SlashCommandErrorEventArgs.cs
+++ b/DisCatSharp.ApplicationCommands/EventArgs/SlashCommand/SlashCommandErrorEventArgs.cs
@@ -1,51 +1,51 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.ApplicationCommands.Context;
using DisCatSharp.EventArgs;
namespace DisCatSharp.ApplicationCommands.EventArgs;
/// <summary>
/// Represents arguments for a <see cref="ApplicationCommandsExtension.SlashCommandErrored"/> event
/// </summary>
public class SlashCommandErrorEventArgs : DiscordEventArgs
{
/// <summary>
/// The context of the command.
/// </summary>
public InteractionContext Context { get; internal set; }
/// <summary>
/// The exception thrown.
/// </summary>
public Exception Exception { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="SlashCommandErrorEventArgs"/> class.
/// </summary>
/// <param name="provider">The provider.</param>
public SlashCommandErrorEventArgs(IServiceProvider provider) : base(provider)
{ }
}
diff --git a/DisCatSharp.ApplicationCommands/EventArgs/SlashCommand/SlashCommandExecutedEventArgs.cs b/DisCatSharp.ApplicationCommands/EventArgs/SlashCommand/SlashCommandExecutedEventArgs.cs
index 8e617deda..c9947c3db 100644
--- a/DisCatSharp.ApplicationCommands/EventArgs/SlashCommand/SlashCommandExecutedEventArgs.cs
+++ b/DisCatSharp.ApplicationCommands/EventArgs/SlashCommand/SlashCommandExecutedEventArgs.cs
@@ -1,46 +1,46 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.ApplicationCommands.Context;
using DisCatSharp.EventArgs;
namespace DisCatSharp.ApplicationCommands.EventArgs;
/// <summary>
/// Represents the arguments for a <see cref="ApplicationCommandsExtension.SlashCommandExecuted"/> event
/// </summary>
public class SlashCommandExecutedEventArgs : DiscordEventArgs
{
/// <summary>
/// The context of the command.
/// </summary>
public InteractionContext Context { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="SlashCommandExecutedEventArgs"/> class.
/// </summary>
/// <param name="provider">The provider.</param>
public SlashCommandExecutedEventArgs(IServiceProvider provider) : base(provider)
{ }
}
diff --git a/DisCatSharp.ApplicationCommands/Exceptions/ContextMenu/ContextMenuExecutionChecksFailedException.cs b/DisCatSharp.ApplicationCommands/Exceptions/ContextMenu/ContextMenuExecutionChecksFailedException.cs
index 71ef65130..eb70b92b8 100644
--- a/DisCatSharp.ApplicationCommands/Exceptions/ContextMenu/ContextMenuExecutionChecksFailedException.cs
+++ b/DisCatSharp.ApplicationCommands/Exceptions/ContextMenu/ContextMenuExecutionChecksFailedException.cs
@@ -1,39 +1,39 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using DisCatSharp.ApplicationCommands.Attributes;
namespace DisCatSharp.ApplicationCommands.Exceptions;
/// <summary>
/// Thrown when a pre-execution check for a context menu command fails.
/// </summary>
public sealed class ContextMenuExecutionChecksFailedException : Exception
{
/// <summary>
/// The list of failed checks.
/// </summary>
public IReadOnlyList<ApplicationCommandCheckBaseAttribute> FailedChecks;
}
diff --git a/DisCatSharp.ApplicationCommands/Exceptions/SlashCommand/SlashExecutionChecksFailedException.cs b/DisCatSharp.ApplicationCommands/Exceptions/SlashCommand/SlashExecutionChecksFailedException.cs
index 2adf352db..d6cedce58 100644
--- a/DisCatSharp.ApplicationCommands/Exceptions/SlashCommand/SlashExecutionChecksFailedException.cs
+++ b/DisCatSharp.ApplicationCommands/Exceptions/SlashCommand/SlashExecutionChecksFailedException.cs
@@ -1,39 +1,39 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using DisCatSharp.ApplicationCommands.Attributes;
namespace DisCatSharp.ApplicationCommands.Exceptions;
/// <summary>
/// Thrown when a pre-execution check for a slash command fails.
/// </summary>
public class SlashExecutionChecksFailedException : Exception
{
/// <summary>
/// The list of failed checks.
/// </summary>
public IReadOnlyList<ApplicationCommandCheckBaseAttribute> FailedChecks;
}
diff --git a/DisCatSharp.ApplicationCommands/ExtensionMethods.cs b/DisCatSharp.ApplicationCommands/ExtensionMethods.cs
index 40069226b..b1e762307 100644
--- a/DisCatSharp.ApplicationCommands/ExtensionMethods.cs
+++ b/DisCatSharp.ApplicationCommands/ExtensionMethods.cs
@@ -1,179 +1,179 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Threading.Tasks;
using DisCatSharp.ApplicationCommands.Attributes;
using DisCatSharp.ApplicationCommands.Context;
namespace DisCatSharp.ApplicationCommands;
/// <summary>
/// Defines various extension methods for application commands.
/// </summary>
public static class ExtensionMethods
{
/// <summary>
/// Enables application commands on this <see cref="DiscordClient"/>.
/// </summary>
/// <param name="client">Client to enable application commands for.</param>
/// <param name="config">Configuration to use.</param>
/// <returns>Created <see cref="ApplicationCommandsExtension"/>.</returns>
public static ApplicationCommandsExtension UseApplicationCommands(this DiscordClient client,
ApplicationCommandsConfiguration config = null)
{
if (client.GetExtension<ApplicationCommandsExtension>() != null)
throw new InvalidOperationException("Application commands are already enabled for that client.");
var scomm = new ApplicationCommandsExtension(config);
client.AddExtension(scomm);
return scomm;
}
/// <summary>
/// Gets the application commands module for this client.
/// </summary>
/// <param name="client">Client to get application commands for.</param>
/// <returns>The module, or null if not activated.</returns>
public static ApplicationCommandsExtension GetApplicationCommands(this DiscordClient client)
=> client.GetExtension<ApplicationCommandsExtension>();
/// <summary>
/// Gets the application commands from this <see cref="DiscordShardedClient"/>.
/// </summary>
/// <param name="client">Client to get application commands from.</param>
/// <returns>A dictionary of current <see cref="ApplicationCommandsExtension"/> with the key being the shard id.</returns>
public static async Task<IReadOnlyDictionary<int, ApplicationCommandsExtension>> GetApplicationCommandsAsync(this DiscordShardedClient client)
{
await client.InitializeShardsAsync().ConfigureAwait(false);
var modules = new Dictionary<int, ApplicationCommandsExtension>();
foreach (var shard in client.ShardClients.Values)
modules.Add(shard.ShardId, shard.GetExtension<ApplicationCommandsExtension>());
return modules;
}
/// <summary>
/// Registers a command class with optional translation setup globally.
/// </summary>
/// <param name="extensions">Sharding extensions.</param>
/// <typeparam name="T">The command class to register.</typeparam>
/// <param name="translationSetup">A callback to setup translations with.</param>
public static void RegisterGlobalCommands<T>(this IReadOnlyDictionary<int, ApplicationCommandsExtension> extensions, Action<ApplicationCommandsTranslationContext> translationSetup = null) where T : ApplicationCommandsModule
{
foreach (var extension in extensions.Values)
extension.RegisterGlobalCommands<T>(translationSetup);
}
/// <summary>
/// Registers a command class with optional translation setup globally.
/// </summary>
/// <param name="extensions">Sharding extensions.</param>
/// <param name="type">The <see cref="System.Type"/> of the command class to register.</param>
/// <param name="translationSetup">A callback to setup translations with.</param>
public static void RegisterGlobalCommands(this IReadOnlyDictionary<int, ApplicationCommandsExtension> extensions, Type type, Action<ApplicationCommandsTranslationContext> translationSetup = null)
{
if (!typeof(ApplicationCommandsModule).IsAssignableFrom(type))
throw new ArgumentException("Command classes have to inherit from ApplicationCommandsModule", nameof(type));
foreach (var extension in extensions.Values)
extension.RegisterGlobalCommands(type, translationSetup);
}
/// <summary>
/// Registers a command class with optional translation setup for a guild.
/// </summary>
/// <typeparam name="T">The command class to register.</typeparam>
/// <param name="extensions">Sharding extensions.</param>
/// <param name="guildId">The guild id to register it on.</param>
/// <param name="translationSetup">A callback to setup translations with.</param>
public static void RegisterGuildCommands<T>(this IReadOnlyDictionary<int, ApplicationCommandsExtension> extensions, ulong guildId, Action<ApplicationCommandsTranslationContext> translationSetup = null) where T : ApplicationCommandsModule
{
foreach (var extension in extensions.Values)
extension.RegisterGuildCommands<T>(guildId, translationSetup);
}
/// <summary>
/// Registers a command class with optional translation setup for a guild.
/// </summary>
/// <param name="extensions">Sharding extensions.</param>
/// <param name="type">The <see cref="System.Type"/> of the command class to register.</param>
/// <param name="guildId">The guild id to register it on.</param>
/// <param name="translationSetup">A callback to setup translations with.</param>
public static void RegisterGuildCommands(this IReadOnlyDictionary<int, ApplicationCommandsExtension> extensions, Type type, ulong guildId, Action<ApplicationCommandsTranslationContext> translationSetup = null)
{
if (!typeof(ApplicationCommandsModule).IsAssignableFrom(type))
throw new ArgumentException("Command classes have to inherit from ApplicationCommandsModule", nameof(type));
foreach (var extension in extensions.Values)
extension.RegisterGuildCommands(type, guildId, translationSetup);
}
/// <summary>
/// Enables application commands on this <see cref="DiscordShardedClient"/>.
/// </summary>
/// <param name="client">Client to enable application commands on.</param>
/// <param name="config">Configuration to use.</param>
/// <returns>A dictionary of created <see cref="ApplicationCommandsExtension"/> with the key being the shard id.</returns>
public static async Task<IReadOnlyDictionary<int, ApplicationCommandsExtension>> UseApplicationCommandsAsync(this DiscordShardedClient client, ApplicationCommandsConfiguration config = null)
{
var modules = new Dictionary<int, ApplicationCommandsExtension>();
await client.InitializeShardsAsync().ConfigureAwait(false);
foreach (var shard in client.ShardClients.Values)
{
var scomm = shard.GetExtension<ApplicationCommandsExtension>();
scomm ??= shard.UseApplicationCommands(config);
modules[shard.ShardId] = scomm;
}
return modules;
}
/// <summary>
/// Gets the name from the <see cref="ChoiceNameAttribute"/> for this enum value.
/// </summary>
/// <returns>The name.</returns>
public static string GetName<T>(this T e) where T : IConvertible
{
if (e is Enum)
{
var type = e.GetType();
var values = Enum.GetValues(type);
foreach (int val in values)
{
if (val == e.ToInt32(CultureInfo.InvariantCulture))
{
var memInfo = type.GetMember(type.GetEnumName(val));
return memInfo[0]
.GetCustomAttributes(typeof(ChoiceNameAttribute), false)
.FirstOrDefault() is ChoiceNameAttribute nameAttribute ? nameAttribute.Name : type.GetEnumName(val);
}
}
}
return null;
}
}
diff --git a/DisCatSharp.ApplicationCommands/GlobalSuppressions.cs b/DisCatSharp.ApplicationCommands/GlobalSuppressions.cs
index e116cb4bc..70efb93de 100644
--- a/DisCatSharp.ApplicationCommands/GlobalSuppressions.cs
+++ b/DisCatSharp.ApplicationCommands/GlobalSuppressions.cs
@@ -1,39 +1,39 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "<Pending>", Scope = "member", Target = "~F:DisCatSharp.ApplicationCommands.ApplicationCommandsExtension.s_registeredCommands")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.ApplicationCommandsExtension.UpdateAsync~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.ApplicationCommands.ApplicationCommandsExtension.GlobalCommands")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.ApplicationCommands.ApplicationCommandsExtension.GuildCommands")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.ApplicationCommands.ApplicationCommandsExtension.RegisteredCommands")]
[assembly: SuppressMessage("Performance", "CA1842:Do not use 'WhenAll' with a single task", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.DefaultHelpModule.DefaultHelpAsync(DisCatSharp.ApplicationCommands.Context.InteractionContext,System.String,System.String,System.String)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Performance", "CA1842:Do not use 'WhenAll' with a single task", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.DefaultHelpModule.DefaultHelpAutoCompleteLevelOneProvider.Provider(DisCatSharp.ApplicationCommands.Context.AutocompleteContext)~System.Threading.Tasks.Task{System.Collections.Generic.IEnumerable{DisCatSharp.Entities.DiscordApplicationCommandAutocompleteChoice}}")]
[assembly: SuppressMessage("Performance", "CA1842:Do not use 'WhenAll' with a single task", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.DefaultHelpModule.DefaultHelpAutoCompleteLevelTwoProvider.Provider(DisCatSharp.ApplicationCommands.Context.AutocompleteContext)~System.Threading.Tasks.Task{System.Collections.Generic.IEnumerable{DisCatSharp.Entities.DiscordApplicationCommandAutocompleteChoice}}")]
[assembly: SuppressMessage("Performance", "CA1842:Do not use 'WhenAll' with a single task", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.DefaultHelpModule.DefaultHelpAutoCompleteProvider.Provider(DisCatSharp.ApplicationCommands.Context.AutocompleteContext)~System.Threading.Tasks.Task{System.Collections.Generic.IEnumerable{DisCatSharp.Entities.DiscordApplicationCommandAutocompleteChoice}}")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.ApplicationCommandsExtension.RunPreexecutionChecksAsync(System.Reflection.MethodInfo,DisCatSharp.ApplicationCommands.Context.BaseContext)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.DefaultHelpModule.DefaultHelpAsync(DisCatSharp.ApplicationCommands.Context.InteractionContext,System.String,System.String,System.String)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.Workers.RegistrationWorker.BuildGlobalCreateList(DisCatSharp.DiscordClient,System.Collections.Generic.List{DisCatSharp.Entities.DiscordApplicationCommand})~System.Collections.Generic.List{DisCatSharp.Entities.DiscordApplicationCommand}")]
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.Workers.RegistrationWorker.BuildGlobalDeleteList(DisCatSharp.DiscordClient,System.Collections.Generic.List{DisCatSharp.Entities.DiscordApplicationCommand})~System.Collections.Generic.List{System.UInt64}")]
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.Workers.RegistrationWorker.BuildGuildCreateList(DisCatSharp.DiscordClient,System.UInt64,System.Collections.Generic.List{DisCatSharp.Entities.DiscordApplicationCommand})~System.Collections.Generic.List{DisCatSharp.Entities.DiscordApplicationCommand}")]
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.ApplicationCommands.Workers.RegistrationWorker.BuildGuildDeleteList(DisCatSharp.DiscordClient,System.UInt64,System.Collections.Generic.List{DisCatSharp.Entities.DiscordApplicationCommand})~System.Collections.Generic.List{System.UInt64}")]
diff --git a/DisCatSharp.ApplicationCommands/Properties/AssemblyProperties.cs b/DisCatSharp.ApplicationCommands/Properties/AssemblyProperties.cs
index 62f2232fc..26e980138 100644
--- a/DisCatSharp.ApplicationCommands/Properties/AssemblyProperties.cs
+++ b/DisCatSharp.ApplicationCommands/Properties/AssemblyProperties.cs
@@ -1,48 +1,48 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("DisCatSharp")]
[assembly: InternalsVisibleTo("DisCatSharp.Experimental")]
[assembly: InternalsVisibleTo("DisCatSharp.CommandsNext")]
[assembly: InternalsVisibleTo("DisCatSharp.Common")]
[assembly: InternalsVisibleTo("DisCatSharp.Configuration")]
[assembly: InternalsVisibleTo("DisCatSharp.Configuration.Tests")]
[assembly: InternalsVisibleTo("DisCatSharp.Hosting")]
[assembly: InternalsVisibleTo("DisCatSharp.Hosting.DependencyInjection")]
[assembly: InternalsVisibleTo("DisCatSharp.Hosting.Tests")]
[assembly: InternalsVisibleTo("DisCatSharp.Interactivity")]
[assembly: InternalsVisibleTo("DisCatSharp.Lavalink")]
[assembly: InternalsVisibleTo("DisCatSharp.Phabricator")]
[assembly: InternalsVisibleTo("DisCatSharp.Support")]
[assembly: InternalsVisibleTo("DisCatSharp.Test")]
[assembly: InternalsVisibleTo("DisCatSharp.VoiceNext")]
[assembly: InternalsVisibleTo("DisCatSharp.VoiceNext.Natives")]
[assembly: InternalsVisibleTo("Nyaw")]
[assembly: InternalsVisibleTo("DisCatSharp.DevTools")]
[assembly: InternalsVisibleTo("DisCatSharp.DocsGenerator")]
[assembly: InternalsVisibleTo("DisCatSharp.StaffApps")]
[assembly: InternalsVisibleTo("DisCatSharp.TranslationGenerator")]
[assembly: InternalsVisibleTo("Microsoft.DocAsCode")]
[assembly: InternalsVisibleTo("Microsoft.DocAsCode.Metadata.ManagedReference")]
diff --git a/DisCatSharp.ApplicationCommands/Workers/ApplicationCommandWorker.cs b/DisCatSharp.ApplicationCommands/Workers/ApplicationCommandWorker.cs
index abdc0560a..1e7c19ff1 100644
--- a/DisCatSharp.ApplicationCommands/Workers/ApplicationCommandWorker.cs
+++ b/DisCatSharp.ApplicationCommands/Workers/ApplicationCommandWorker.cs
@@ -1,371 +1,371 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.ApplicationCommands.Attributes;
using DisCatSharp.ApplicationCommands.Context;
using DisCatSharp.ApplicationCommands.Entities;
using DisCatSharp.ApplicationCommands.Enums;
using DisCatSharp.Entities;
using DisCatSharp.Enums;
namespace DisCatSharp.ApplicationCommands.Workers;
/// <summary>
/// Represents a <see cref="CommandWorker"/>.
/// </summary>
internal class CommandWorker
{
/// <summary>
/// Parses context menu application commands.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="methods">List of method infos.</param>
/// <param name="translator">The optional command translations.</param>
/// <returns>Too much.</returns>
internal static Task<
(
List<DiscordApplicationCommand> applicationCommands,
List<KeyValuePair<Type, Type>> commandTypeSources,
List<ContextMenuCommand> contextMenuCommands,
bool withLocalization
)
> ParseContextMenuCommands(Type type, IEnumerable<MethodInfo> methods, List<CommandTranslator> translator = null)
{
List<DiscordApplicationCommand> commands = new();
List<KeyValuePair<Type, Type>> commandTypeSources = new();
List<ContextMenuCommand> contextMenuCommands = new();
foreach (var contextMethod in methods)
{
var contextAttribute = contextMethod.GetCustomAttribute<ContextMenuAttribute>();
DiscordApplicationCommandLocalization nameLocalizations = null;
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.Type, nameLocalizations, null, contextAttribute.DefaultMemberPermissions, contextAttribute.DmPermission ?? true, isNsfw: contextAttribute.IsNsfw);
var parameters = contextMethod.GetParameters();
if (parameters.Length == 0 || parameters == null || !ReferenceEquals(parameters.FirstOrDefault()?.ParameterType, typeof(ContextMenuContext)))
throw new ArgumentException($"The first argument of the command '{contextAttribute.Name}' has to be an ContextMenuContext!");
if (parameters.Length > 1)
throw new ArgumentException($"The context menu command '{contextAttribute.Name}' cannot have parameters!");
contextMenuCommands.Add(new ContextMenuCommand { Method = contextMethod, Name = contextAttribute.Name });
commands.Add(command);
commandTypeSources.Add(new KeyValuePair<Type, Type>(type, type));
}
return Task.FromResult((commands, commandTypeSources, contextMenuCommands, translator != null));
}
/// <summary>
/// Parses single application commands.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="methods">List of method infos.</param>
/// <param name="guildId">The optional guild id.</param>
/// <param name="translator">The optional command translations.</param>
/// <returns>Too much.</returns>
internal static async Task<
(
List<DiscordApplicationCommand> applicationCommands,
List<KeyValuePair<Type, Type>> commandTypeSources,
List<CommandMethod> commandMethods,
bool withLocalization
)
> ParseBasicSlashCommandsAsync(Type type, IEnumerable<MethodInfo> methods, ulong? guildId = null, List<CommandTranslator> translator = null)
{
List<DiscordApplicationCommand> commands = new();
List<KeyValuePair<Type, Type>> commandTypeSources = new();
List<CommandMethod> commandMethods = new();
foreach (var method in methods)
{
var commandAttribute = method.GetCustomAttribute<SlashCommandAttribute>();
var parameters = method.GetParameters();
if (parameters.Length == 0 || parameters == null || !ReferenceEquals(parameters.FirstOrDefault()?.ParameterType, typeof(InteractionContext)))
throw new ArgumentException($"The first argument of the command '{commandAttribute.Name}' has to be an InteractionContext!");
var options = await ApplicationCommandsExtension.ParseParametersAsync(parameters.Skip(1), commandAttribute.Name, guildId);
commandMethods.Add(new CommandMethod { Method = method, Name = commandAttribute.Name });
DiscordApplicationCommandLocalization nameLocalizations = null;
DiscordApplicationCommandLocalization descriptionLocalizations = null;
List<DiscordApplicationCommandOption> localizedOptions = null;
var commandTranslation = translator?.Single(c => c.Name == commandAttribute.Name && c.Type == ApplicationCommandType.ChatInput);
if (commandTranslation != null && commandTranslation.Options != null)
{
localizedOptions = new List<DiscordApplicationCommandOption>(options.Count);
foreach (var option in options)
{
var choices = option.Choices != null ? new List<DiscordApplicationCommandOptionChoice>(option.Choices.Count) : null;
if (option.Choices != null)
foreach (var choice in option.Choices)
choices.Add(new DiscordApplicationCommandOptionChoice(choice.Name, choice.Value, commandTranslation.Options.Single(o => o.Name == option.Name).Choices.Single(c => c.Name == choice.Name).NameTranslations));
localizedOptions.Add(new DiscordApplicationCommandOption(option.Name, option.Description, option.Type, option.Required,
choices, option.Options, option.ChannelTypes, option.AutoComplete, option.MinimumValue, option.MaximumValue,
commandTranslation.Options.Single(o => o.Name == option.Name).NameTranslations, commandTranslation.Options.Single(o => o.Name == option.Name).DescriptionTranslations,
option.MinimumLength, option.MaximumLength
));
}
nameLocalizations = commandTranslation.NameTranslations;
descriptionLocalizations = commandTranslation.DescriptionTranslations;
}
var payload = new DiscordApplicationCommand(commandAttribute.Name, commandAttribute.Description, (localizedOptions != null && localizedOptions.Any() ? localizedOptions : null) ?? (options != null && options.Any() ? options : null), ApplicationCommandType.ChatInput, nameLocalizations, descriptionLocalizations, commandAttribute.DefaultMemberPermissions, commandAttribute.DmPermission ?? true, isNsfw: commandAttribute.IsNsfw);
commands.Add(payload);
commandTypeSources.Add(new KeyValuePair<Type, Type>(type, type));
}
return (commands, commandTypeSources, commandMethods, translator != null);
}
}
/// <summary>
/// Represents a <see cref="NestedCommandWorker"/>.
/// </summary>
internal class NestedCommandWorker
{
/// <summary>
/// Parses application command groups.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="types">List of type infos.</param>
/// <param name="guildId">The optional guild id.</param>
/// <param name="translator">The optional group translations.</param>
/// <returns>Too much.</returns>
internal static async Task<
(
List<DiscordApplicationCommand> applicationCommands,
List<KeyValuePair<Type, Type>> commandTypeSources,
List<object> singletonModules,
List<GroupCommand> groupCommands,
List<SubGroupCommand> subGroupCommands,
bool withLocalization
)
> ParseSlashGroupsAsync(Type type, List<TypeInfo> types, ulong? guildId = null, List<GroupTranslator> translator = null)
{
List<DiscordApplicationCommand> commands = new();
List<KeyValuePair<Type, Type>> commandTypeSources = new();
List<GroupCommand> groupCommands = new();
List<SubGroupCommand> subGroupCommands = new();
List<object> singletonModules = new();
//Handles groups
foreach (var subclassInfo in types)
{
//Gets the attribute and methods in the group
var groupAttribute = subclassInfo.GetCustomAttribute<SlashCommandGroupAttribute>();
var submethods = subclassInfo.DeclaredMethods.Where(x => x.GetCustomAttribute<SlashCommandAttribute>() != null).ToList();
var subclasses = subclassInfo.DeclaredNestedTypes.Where(x => x.GetCustomAttribute<SlashCommandGroupAttribute>() != null).ToList();
if (subclasses.Any() && submethods.Any())
throw new ArgumentException($"Slash command group '{groupAttribute.Name}' has both subcommands and subgroups!");
DiscordApplicationCommandLocalization nameLocalizations = null;
DiscordApplicationCommandLocalization descriptionLocalizations = null;
if (translator != null)
{
var commandTranslation = translator.Single(c => c.Name == groupAttribute.Name);
if (commandTranslation != null)
{
nameLocalizations = commandTranslation.NameTranslations;
descriptionLocalizations = commandTranslation.DescriptionTranslations;
}
}
//Initializes the command
var payload = new DiscordApplicationCommand(groupAttribute.Name, groupAttribute.Description, nameLocalizations: nameLocalizations, descriptionLocalizations: descriptionLocalizations, defaultMemberPermissions: groupAttribute.DefaultMemberPermissions, dmPermission: groupAttribute.DmPermission ?? true, isNsfw: groupAttribute.IsNsfw);
commandTypeSources.Add(new KeyValuePair<Type, Type>(type, type));
var commandMethods = new List<KeyValuePair<string, MethodInfo>>();
//Handles commands in the group
foreach (var submethod in submethods)
{
var commandAttribute = submethod.GetCustomAttribute<SlashCommandAttribute>();
//Gets the parameters 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 of the command '{commandAttribute.Name}' has to be an InteractionContext!");
var options = await ApplicationCommandsExtension.ParseParametersAsync(parameters.Skip(1), commandAttribute.Name, guildId);
DiscordApplicationCommandLocalization subNameLocalizations = null;
DiscordApplicationCommandLocalization subDescriptionLocalizations = null;
List<DiscordApplicationCommandOption> localizedOptions = null;
var commandTranslation = translator?.Single(c => c.Name == payload.Name);
if (commandTranslation?.Commands != null)
{
var subCommandTranslation = commandTranslation.Commands.Single(sc => sc.Name == commandAttribute.Name);
if (subCommandTranslation.Options != null)
{
localizedOptions = new List<DiscordApplicationCommandOption>(options.Count);
foreach (var option in options)
{
var choices = option.Choices != null ? new List<DiscordApplicationCommandOptionChoice>(option.Choices.Count) : null;
if (option.Choices != null)
foreach (var choice in option.Choices)
choices.Add(new DiscordApplicationCommandOptionChoice(choice.Name, choice.Value, subCommandTranslation.Options.Single(o => o.Name == option.Name).Choices.Single(c => c.Name == choice.Name).NameTranslations));
localizedOptions.Add(new DiscordApplicationCommandOption(option.Name, option.Description, option.Type, option.Required,
choices, option.Options, option.ChannelTypes, option.AutoComplete, option.MinimumValue, option.MaximumValue,
subCommandTranslation.Options.Single(o => o.Name == option.Name).NameTranslations, subCommandTranslation.Options.Single(o => o.Name == option.Name).DescriptionTranslations,
option.MinimumLength, option.MaximumLength
));
}
}
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, false, null, localizedOptions ?? options, nameLocalizations: subNameLocalizations, descriptionLocalizations: subDescriptionLocalizations);
payload = new DiscordApplicationCommand(payload.Name, payload.Description, payload.Options?.Append(subpayload) ?? new[] { subpayload }, nameLocalizations: payload.NameLocalizations, descriptionLocalizations: payload.DescriptionLocalizations, defaultMemberPermissions: payload.DefaultMemberPermissions, dmPermission: payload.DmPermission ?? true, isNsfw: payload.IsNsfw);
commandTypeSources.Add(new KeyValuePair<Type, Type>(subclassInfo, type));
//Adds it to the method lists
commandMethods.Add(new KeyValuePair<string, MethodInfo>(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<SlashCommandGroupAttribute>();
var subsubmethods = subclass.DeclaredMethods.Where(x => x.GetCustomAttribute<SlashCommandAttribute>() != null);
var options = new List<DiscordApplicationCommandOption>();
var currentMethods = new List<KeyValuePair<string, MethodInfo>>();
DiscordApplicationCommandLocalization subNameLocalizations = null;
DiscordApplicationCommandLocalization subDescriptionLocalizations = null;
if (translator != null)
{
var commandTranslation = translator.Single(c => c.Name == payload.Name);
if (commandTranslation != null && commandTranslation.SubGroups != null)
{
var subCommandTranslation = commandTranslation.SubGroups.Single(sc => sc.Name == subgroupAttribute.Name);
if (subCommandTranslation != null)
{
subNameLocalizations = subCommandTranslation.NameTranslations;
subDescriptionLocalizations = subCommandTranslation.DescriptionTranslations;
}
}
}
//Similar to the one for regular groups
foreach (var subsubmethod in subsubmethods)
{
var suboptions = new List<DiscordApplicationCommandOption>();
var commatt = subsubmethod.GetCustomAttribute<SlashCommandAttribute>();
var parameters = subsubmethod.GetParameters();
if (parameters.Length == 0 || parameters == null || !ReferenceEquals(parameters.First().ParameterType, typeof(InteractionContext)))
throw new ArgumentException($"The first argument of the command '{subgroupAttribute.Name}' has to be an InteractionContext!");
suboptions = suboptions.Concat(await ApplicationCommandsExtension.ParseParametersAsync(parameters.Skip(1), subgroupAttribute.Name, guildId)).ToList();
DiscordApplicationCommandLocalization subSubNameLocalizations = null;
DiscordApplicationCommandLocalization subSubDescriptionLocalizations = null;
List<DiscordApplicationCommandOption> localizedOptions = null;
var commandTranslation = translator?.Single(c => c.Name == payload.Name);
var subCommandTranslation = commandTranslation?.SubGroups?.Single(sc => sc.Name == commatt.Name);
var subSubCommandTranslation = subCommandTranslation?.Commands.Single(sc => sc.Name == commatt.Name);
if (subSubCommandTranslation != null && subSubCommandTranslation.Options != null)
{
localizedOptions = new List<DiscordApplicationCommandOption>(suboptions.Count);
foreach (var option in suboptions)
{
var choices = option.Choices != null ? new List<DiscordApplicationCommandOptionChoice>(option.Choices.Count) : null;
if (option.Choices != null)
foreach (var choice in option.Choices)
choices.Add(new DiscordApplicationCommandOptionChoice(choice.Name, choice.Value, subSubCommandTranslation.Options.Single(o => o.Name == option.Name).Choices.Single(c => c.Name == choice.Name).NameTranslations));
localizedOptions.Add(new DiscordApplicationCommandOption(option.Name, option.Description, option.Type, option.Required,
choices, option.Options, option.ChannelTypes, option.AutoComplete, option.MinimumValue, option.MaximumValue,
subSubCommandTranslation.Options.Single(o => o.Name == option.Name).NameTranslations, subSubCommandTranslation.Options.Single(o => o.Name == option.Name).DescriptionTranslations,
option.MinimumLength, option.MaximumLength
));
}
subSubNameLocalizations = subSubCommandTranslation.NameTranslations;
subSubDescriptionLocalizations = subSubCommandTranslation.DescriptionTranslations;
}
var subsubpayload = new DiscordApplicationCommandOption(commatt.Name, commatt.Description, ApplicationCommandOptionType.SubCommand, false, null, (localizedOptions != null && localizedOptions.Any() ? localizedOptions : null) ?? (suboptions != null && suboptions.Any() ? suboptions : null), nameLocalizations: subSubNameLocalizations, descriptionLocalizations: subSubDescriptionLocalizations);
options.Add(subsubpayload);
commandMethods.Add(new KeyValuePair<string, MethodInfo>(commatt.Name, subsubmethod));
currentMethods.Add(new KeyValuePair<string, MethodInfo>(commatt.Name, subsubmethod));
}
//Adds the group to the command and method lists
var subpayload = new DiscordApplicationCommandOption(subgroupAttribute.Name, subgroupAttribute.Description, ApplicationCommandOptionType.SubCommandGroup, false, 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 }, nameLocalizations: payload.NameLocalizations, descriptionLocalizations: payload.DescriptionLocalizations, defaultMemberPermissions: payload.DefaultMemberPermissions, dmPermission: payload.DmPermission ?? true, isNsfw: payload.IsNsfw);
commandTypeSources.Add(new KeyValuePair<Type, Type>(subclass, type));
//Accounts for lifespans for the sub group
if (subclass.GetCustomAttribute<ApplicationCommandModuleLifespanAttribute>() != null && subclass.GetCustomAttribute<ApplicationCommandModuleLifespanAttribute>().Lifespan == ApplicationCommandModuleLifespan.Singleton)
singletonModules.Add(ApplicationCommandsExtension.CreateInstance(subclass, ApplicationCommandsExtension.Configuration?.ServiceProvider));
}
if (command.SubCommands.Any()) subGroupCommands.Add(command);
commands.Add(payload);
//Accounts for lifespans
if (subclassInfo.GetCustomAttribute<ApplicationCommandModuleLifespanAttribute>() != null && subclassInfo.GetCustomAttribute<ApplicationCommandModuleLifespanAttribute>().Lifespan == ApplicationCommandModuleLifespan.Singleton)
singletonModules.Add(ApplicationCommandsExtension.CreateInstance(subclassInfo, ApplicationCommandsExtension.Configuration?.ServiceProvider));
}
return (commands, commandTypeSources, singletonModules, groupCommands, subGroupCommands, translator != null);
}
}
diff --git a/DisCatSharp.ApplicationCommands/Workers/RegistrationWorker.cs b/DisCatSharp.ApplicationCommands/Workers/RegistrationWorker.cs
index 1a9dd069b..a246f5fcf 100644
--- a/DisCatSharp.ApplicationCommands/Workers/RegistrationWorker.cs
+++ b/DisCatSharp.ApplicationCommands/Workers/RegistrationWorker.cs
@@ -1,541 +1,541 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.ApplicationCommands.Checks;
using DisCatSharp.Common;
using DisCatSharp.Entities;
using DisCatSharp.Exceptions;
using Microsoft.Extensions.Logging;
namespace DisCatSharp.ApplicationCommands.Workers;
/// <summary>
/// Represents a <see cref="RegistrationWorker"/>.
/// </summary>
internal class RegistrationWorker
{
/// <summary>
/// Registers the global commands.
/// </summary>
/// <param name="client">The discord client.</param>
/// <param name="commands">The command list.</param>
/// <returns>A list of registered commands.</returns>
internal static async Task<List<DiscordApplicationCommand>> RegisterGlobalCommandsAsync(DiscordClient client, List<DiscordApplicationCommand> commands)
{
var (changedCommands, unchangedCommands) = BuildGlobalOverwriteList(client, commands);
var globalCommandsCreateList = BuildGlobalCreateList(client, commands);
var globalCommandsDeleteList = BuildGlobalDeleteList(client, commands);
if (globalCommandsCreateList.NotEmptyAndNotNull() && unchangedCommands.NotEmptyAndNotNull() && changedCommands.NotEmptyAndNotNull())
{
client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, $"[AC GLOBAL] Creating, re-using and overwriting application commands.");
foreach (var cmd in globalCommandsCreateList)
{
var discordBackendCommand = await client.CreateGlobalApplicationCommandAsync(cmd);
commands.Add(discordBackendCommand);
}
foreach (var cmd in changedCommands)
{
var command = cmd.Value;
var discordBackendCommand = await client.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 = Optional.Some(command.Options);
action.DefaultMemberPermissions = command.DefaultMemberPermissions;
action.DmPermission = command.DmPermission ?? true;
action.IsNsfw = command.IsNsfw;
});
commands.Add(discordBackendCommand);
}
commands.AddRange(unchangedCommands);
}
else if (globalCommandsCreateList.NotEmptyAndNotNull() && (unchangedCommands.NotEmptyAndNotNull() || changedCommands.NotEmptyAndNotNull()))
{
client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, $"[AC GLOBAL] Creating, re-using and overwriting application commands.");
foreach (var cmd in globalCommandsCreateList)
{
var discordBackendCommand = await client.CreateGlobalApplicationCommandAsync(cmd);
commands.Add(discordBackendCommand);
}
if (changedCommands.NotEmptyAndNotNull())
foreach (var cmd in changedCommands)
{
var command = cmd.Value;
var discordBackendCommand = await client.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 = Optional.Some(command.Options);
action.DefaultMemberPermissions = command.DefaultMemberPermissions;
action.DmPermission = command.DmPermission ?? true;
action.IsNsfw = command.IsNsfw;
});
commands.Add(discordBackendCommand);
}
if (unchangedCommands.NotEmptyAndNotNull())
commands.AddRange(unchangedCommands);
}
else if (globalCommandsCreateList.EmptyOrNull() && unchangedCommands.NotEmptyAndNotNull() && changedCommands.NotEmptyAndNotNull())
{
client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, $"[AC GLOBAL] Editing & re-using application commands.");
foreach (var cmd in changedCommands)
{
var command = cmd.Value;
var discordBackendCommand = await client.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 = Optional.Some(command.Options);
action.DefaultMemberPermissions = command.DefaultMemberPermissions;
action.DmPermission = command.DmPermission ?? true;
action.IsNsfw = command.IsNsfw;
});
commands.Add(discordBackendCommand);
}
commands.AddRange(unchangedCommands);
}
else if (globalCommandsCreateList.EmptyOrNull() && changedCommands.NotEmptyAndNotNull() && unchangedCommands.EmptyOrNull())
{
client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, $"[AC GLOBAL] Overwriting all application commands.");
List<DiscordApplicationCommand> overwriteList = new();
foreach (var overwrite in changedCommands)
{
var cmd = overwrite.Value;
cmd.Id = overwrite.Key;
overwriteList.Add(cmd);
}
var discordBackendCommands = await client.BulkOverwriteGlobalApplicationCommandsAsync(overwriteList);
commands.AddRange(discordBackendCommands);
}
else if (globalCommandsCreateList.NotEmptyAndNotNull() && changedCommands.EmptyOrNull() && unchangedCommands.EmptyOrNull())
{
client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, $"[AC GLOBAL] Creating all application commands.");
var cmds = await client.BulkOverwriteGlobalApplicationCommandsAsync(globalCommandsCreateList);
commands.AddRange(cmds);
}
else if (globalCommandsCreateList.EmptyOrNull() && changedCommands.EmptyOrNull() && unchangedCommands.NotEmptyAndNotNull())
{
client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, $"[AC GLOBAL] Re-using all application commands.");
commands.AddRange(unchangedCommands);
}
if (globalCommandsDeleteList.NotEmptyAndNotNull())
{
client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, $"[AC GLOBAL] Deleting missing application commands.");
foreach (var cmdId in globalCommandsDeleteList)
try
{
await client.DeleteGlobalApplicationCommandAsync(cmdId);
}
catch (NotFoundException)
{
client.Logger.LogError(@"Could not delete global command {cmd}. Please clean up manually", cmdId);
}
}
return commands.NotEmptyAndNotNull() ? commands : null;
}
/// <summary>
/// Registers the guild commands.
/// </summary>
/// <param name="client">The discord client.</param>
/// <param name="guildId">The target guild id.</param>
/// <param name="commands">The command list.</param>
/// <returns>A list of registered commands.</returns>
internal static async Task<List<DiscordApplicationCommand>> RegisterGuildCommandsAsync(DiscordClient client, ulong guildId, List<DiscordApplicationCommand> commands)
{
var (changedCommands, unchangedCommands) = BuildGuildOverwriteList(client, guildId, commands);
var guildCommandsCreateList = BuildGuildCreateList(client, guildId, commands);
var guildCommandsDeleteList = BuildGuildDeleteList(client, guildId, commands);
if (guildCommandsCreateList.NotEmptyAndNotNull() && unchangedCommands.NotEmptyAndNotNull() && changedCommands.NotEmptyAndNotNull())
{
client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, @"[AC GUILD] Creating, re-using and overwriting application commands. Guild ID: {guild}", guildId);
foreach (var cmd in guildCommandsCreateList)
{
var discordBackendCommand = await client.CreateGuildApplicationCommandAsync(guildId, cmd);
commands.Add(discordBackendCommand);
}
foreach (var cmd in changedCommands)
{
var command = cmd.Value;
var discordBackendCommand = await client.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 = Optional.Some(command.Options);
action.DefaultMemberPermissions = command.DefaultMemberPermissions;
action.DmPermission = command.DmPermission ?? true;
action.IsNsfw = command.IsNsfw;
});
commands.Add(discordBackendCommand);
}
commands.AddRange(unchangedCommands);
}
else if (guildCommandsCreateList.NotEmptyAndNotNull() && (unchangedCommands.NotEmptyAndNotNull() || changedCommands.NotEmptyAndNotNull()))
{
client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, @"[AC GUILD] Creating, re-using and overwriting application commands. Guild ID: {guild}", guildId);
foreach (var cmd in guildCommandsCreateList)
{
var discordBackendCommand = await client.CreateGuildApplicationCommandAsync(guildId, cmd);
commands.Add(discordBackendCommand);
}
if (changedCommands.NotEmptyAndNotNull())
foreach (var cmd in changedCommands)
{
var command = cmd.Value;
var discordBackendCommand = await client.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 = Optional.Some(command.Options);
action.DefaultMemberPermissions = command.DefaultMemberPermissions;
action.DmPermission = command.DmPermission ?? true;
action.IsNsfw = command.IsNsfw;
});
commands.Add(discordBackendCommand);
}
if (unchangedCommands.NotEmptyAndNotNull())
commands.AddRange(unchangedCommands);
}
else if (guildCommandsCreateList.EmptyOrNull() && unchangedCommands.NotEmptyAndNotNull() && changedCommands.NotEmptyAndNotNull())
{
client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, @"[AC GUILD] Editing & re-using application commands. Guild ID: {guild}", guildId);
foreach (var cmd in changedCommands)
{
var command = cmd.Value;
var discordBackendCommand = await client.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 = Optional.Some(command.Options);
if (command.DefaultMemberPermissions.HasValue)
action.DefaultMemberPermissions = command.DefaultMemberPermissions.Value;
if (command.DmPermission.HasValue)
action.DmPermission = command.DmPermission.Value;
action.IsNsfw = command.IsNsfw;
});
commands.Add(discordBackendCommand);
}
commands.AddRange(unchangedCommands);
}
else if (guildCommandsCreateList.EmptyOrNull() && changedCommands.NotEmptyAndNotNull() && unchangedCommands.EmptyOrNull())
{
client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, @"[AC GUILD] Overwriting all application commands. Guild ID: {guild}", guildId);
List<DiscordApplicationCommand> overwriteList = new();
foreach (var overwrite in changedCommands)
{
var cmd = overwrite.Value;
cmd.Id = overwrite.Key;
overwriteList.Add(cmd);
}
var discordBackendCommands = await client.BulkOverwriteGuildApplicationCommandsAsync(guildId, overwriteList);
commands.AddRange(discordBackendCommands);
}
else if (guildCommandsCreateList.NotEmptyAndNotNull() && changedCommands.EmptyOrNull() && unchangedCommands.EmptyOrNull())
{
client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, @"[AC GUILD] Creating all application commands. Guild ID: {guild}", guildId);
var cmds = await client.BulkOverwriteGuildApplicationCommandsAsync(guildId, guildCommandsCreateList);
commands.AddRange(cmds);
}
else if (guildCommandsCreateList.EmptyOrNull() && changedCommands.EmptyOrNull() && unchangedCommands.NotEmptyAndNotNull())
{
client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, @"[AC GUILD] Re-using all application commands Guild ID: {guild}.", guildId);
commands.AddRange(unchangedCommands);
}
if (guildCommandsDeleteList.NotEmptyAndNotNull())
foreach (var cmdId in guildCommandsDeleteList)
{
client.Logger.Log(ApplicationCommandsExtension.ApplicationCommandsLogLevel, @"[AC GUILD] Deleting missing application commands. Guild ID: {guild}", guildId);
try
{
await client.DeleteGuildApplicationCommandAsync(guildId, cmdId);
}
catch (NotFoundException)
{
client.Logger.LogError(@"Could not delete guild command {cmd} in guild {guild}. Please clean up manually", cmdId, guildId);
}
}
return commands.NotEmptyAndNotNull() ? commands : null;
}
/// <summary>
/// Builds a list of guild command ids to be deleted on discords backend.
/// </summary>
/// <param name="client">The discord client.</param>
/// <param name="guildId">The guild id these commands belong to.</param>
/// <param name="updateList">The command list.</param>
/// <returns>A list of command ids.</returns>
private static List<ulong> BuildGuildDeleteList(DiscordClient client, ulong guildId, List<DiscordApplicationCommand> updateList)
{
if (ApplicationCommandsExtension.GuildDiscordCommands == null || !ApplicationCommandsExtension.GuildDiscordCommands.Any()
|| !ApplicationCommandsExtension.GuildDiscordCommands.GetFirstValueByKey(guildId, out var discord)
)
return null;
List<ulong> invalidCommandIds = new();
if (discord == null)
return null;
if (updateList == null)
foreach (var cmd in discord)
invalidCommandIds.Add(cmd.Id);
else
foreach (var cmd in discord)
if (!updateList.Any(ul => ul.Name == cmd.Name))
invalidCommandIds.Add(cmd.Id);
return invalidCommandIds;
}
/// <summary>
/// Builds a list of guild commands to be created on discords backend.
/// </summary>
/// <param name="client">The discord client.</param>
/// <param name="guildId">The guild id these commands belong to.</param>
/// <param name="updateList">The command list.</param>
/// <returns></returns>
private static List<DiscordApplicationCommand> BuildGuildCreateList(DiscordClient client, ulong guildId, List<DiscordApplicationCommand> updateList)
{
if (ApplicationCommandsExtension.GuildDiscordCommands == null || !ApplicationCommandsExtension.GuildDiscordCommands.Any()
|| updateList == null || !ApplicationCommandsExtension.GuildDiscordCommands.GetFirstValueByKey(guildId, out var discord)
)
return updateList;
List<DiscordApplicationCommand> newCommands = new();
if (discord == null)
return updateList;
foreach (var cmd in updateList)
if (discord.All(d => d.Name != cmd.Name))
newCommands.Add(cmd);
return newCommands;
}
/// <summary>
/// Builds a list of guild commands to be overwritten on discords backend.
/// </summary>
/// <param name="client">The discord client.</param>
/// <param name="guildId">The guild id these commands belong to.</param>
/// <param name="updateList">The command list.</param>
/// <returns>A dictionary of command id and command.</returns>
private static (
Dictionary<ulong, DiscordApplicationCommand> changedCommands,
List<DiscordApplicationCommand> unchangedCommands
) BuildGuildOverwriteList(DiscordClient client, ulong guildId, List<DiscordApplicationCommand> updateList)
{
if (ApplicationCommandsExtension.GuildDiscordCommands == null || !ApplicationCommandsExtension.GuildDiscordCommands.Any()
|| ApplicationCommandsExtension.GuildDiscordCommands.All(l => l.Key != guildId) || updateList == null
|| !ApplicationCommandsExtension.GuildDiscordCommands.GetFirstValueByKey(guildId, out var discord)
)
return (null, null);
Dictionary<ulong, DiscordApplicationCommand> updateCommands = new();
List<DiscordApplicationCommand> unchangedCommands = new();
if (discord == null)
return (null, null);
foreach (var cmd in updateList)
if (discord.GetFirstValueWhere(d => d.Name == cmd.Name, out var command))
if (command.IsEqualTo(cmd, client, true))
{
if (ApplicationCommandsExtension.DebugEnabled)
client.Logger.LogDebug(@"[AC] Command {cmdName} unchanged", cmd.Name);
cmd.Id = command.Id;
cmd.ApplicationId = command.ApplicationId;
cmd.Version = command.Version;
unchangedCommands.Add(cmd);
}
else
{
if (ApplicationCommandsExtension.DebugEnabled)
client.Logger.LogDebug(@"[AC] Command {cmdName} changed", cmd.Name);
updateCommands.Add(command.Id, cmd);
}
return (updateCommands, unchangedCommands);
}
/// <summary>
/// Builds a list of global command ids to be deleted on discords backend.
/// </summary>
/// <param name="client">The discord client.</param>
/// <param name="updateList">The command list.</param>
/// <returns>A list of command ids.</returns>
private static List<ulong> BuildGlobalDeleteList(DiscordClient client, List<DiscordApplicationCommand> updateList = null)
{
if (ApplicationCommandsExtension.GlobalDiscordCommands == null || !ApplicationCommandsExtension.GlobalDiscordCommands.Any()
|| ApplicationCommandsExtension.GlobalDiscordCommands == null
)
return null;
var discord = ApplicationCommandsExtension.GlobalDiscordCommands;
List<ulong> invalidCommandIds = new();
if (discord == null)
return null;
if (updateList == null)
foreach (var cmd in discord)
invalidCommandIds.Add(cmd.Id);
else
foreach (var cmd in discord)
if (updateList.All(ul => ul.Name != cmd.Name))
invalidCommandIds.Add(cmd.Id);
return invalidCommandIds;
}
/// <summary>
/// Builds a list of global commands to be created on discords backend.
/// </summary>
/// <param name="client">The discord client.</param>
/// <param name="updateList">The command list.</param>
/// <returns>A list of commands.</returns>
private static List<DiscordApplicationCommand> BuildGlobalCreateList(DiscordClient client, List<DiscordApplicationCommand> updateList)
{
if (ApplicationCommandsExtension.GlobalDiscordCommands == null || !ApplicationCommandsExtension.GlobalDiscordCommands.Any() || updateList == null)
return updateList;
var discord = ApplicationCommandsExtension.GlobalDiscordCommands;
List<DiscordApplicationCommand> newCommands = new();
if (discord == null)
return updateList;
foreach (var cmd in updateList)
if (discord.All(d => d.Name != cmd.Name))
newCommands.Add(cmd);
return newCommands;
}
/// <summary>
/// Builds a list of global commands to be overwritten on discords backend.
/// </summary>
/// <param name="client">The discord client.</param>
/// <param name="updateList">The command list.</param>
/// <returns>A dictionary of command ids and commands.</returns>
private static (
Dictionary<ulong, DiscordApplicationCommand> changedCommands,
List<DiscordApplicationCommand> unchangedCommands
) BuildGlobalOverwriteList(DiscordClient client, List<DiscordApplicationCommand> updateList)
{
if (ApplicationCommandsExtension.GlobalDiscordCommands == null || !ApplicationCommandsExtension.GlobalDiscordCommands.Any()
|| updateList == null || ApplicationCommandsExtension.GlobalDiscordCommands == null
)
return (null, null);
var discord = ApplicationCommandsExtension.GlobalDiscordCommands;
if (discord == null)
return (null, null);
Dictionary<ulong, DiscordApplicationCommand> updateCommands = new();
List<DiscordApplicationCommand> unchangedCommands = new();
foreach (var cmd in updateList)
if (discord.GetFirstValueWhere(d => d.Name == cmd.Name, out var command))
if (command.IsEqualTo(cmd, client, false))
{
if (ApplicationCommandsExtension.DebugEnabled)
client.Logger.LogDebug(@"[AC] Command {cmdName} unchanged", cmd.Name);
cmd.Id = command.Id;
cmd.ApplicationId = command.ApplicationId;
cmd.Version = command.Version;
unchangedCommands.Add(cmd);
}
else
{
if (ApplicationCommandsExtension.DebugEnabled)
client.Logger.LogDebug(@"[AC] Command {cmdName} changed", cmd.Name);
updateCommands.Add(command.Id, cmd);
}
return (updateCommands, unchangedCommands);
}
}
diff --git a/DisCatSharp.CommandsNext/Attributes/AliasesAttribute.cs b/DisCatSharp.CommandsNext/Attributes/AliasesAttribute.cs
index 613127b2c..1db830630 100644
--- a/DisCatSharp.CommandsNext/Attributes/AliasesAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/AliasesAttribute.cs
@@ -1,52 +1,52 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
namespace DisCatSharp.CommandsNext.Attributes;
/// <summary>
/// Adds aliases to this command or group.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public sealed class AliasesAttribute : Attribute
{
/// <summary>
/// Gets this group's aliases.
/// </summary>
public IReadOnlyList<string> Aliases { get; }
/// <summary>
/// Adds aliases to this command or group.
/// </summary>
/// <param name="aliases">Aliases to add to this command or group.</param>
public AliasesAttribute(params string[] aliases)
{
if (aliases.Any(xa => xa == null || xa.Any(xc => char.IsWhiteSpace(xc))))
throw new ArgumentException("Aliases cannot contain whitespace characters or null strings.", nameof(aliases));
this.Aliases = new ReadOnlyCollection<string>(aliases);
}
}
diff --git a/DisCatSharp.CommandsNext/Attributes/CheckBaseAttribute.cs b/DisCatSharp.CommandsNext/Attributes/CheckBaseAttribute.cs
index b3b040a39..6fa659c4b 100644
--- a/DisCatSharp.CommandsNext/Attributes/CheckBaseAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/CheckBaseAttribute.cs
@@ -1,41 +1,41 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a base for all command pre-execution check attributes.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public abstract class CheckBaseAttribute : Attribute
{
/// <summary>
/// Asynchronously checks whether this command can be executed within given context.
/// </summary>
/// <param name="ctx">Context to check execution ability for.</param>
/// <param name="help">Whether this check is being executed from help or not. This can be used to probe whether command can be run without setting off certain fail conditions (such as cooldowns).</param>
/// <returns>Whether the command can be executed in given context.</returns>
public abstract Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help);
}
diff --git a/DisCatSharp.CommandsNext/Attributes/CommandAttribute.cs b/DisCatSharp.CommandsNext/Attributes/CommandAttribute.cs
index 02758619f..14f7fab5b 100644
--- a/DisCatSharp.CommandsNext/Attributes/CommandAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/CommandAttribute.cs
@@ -1,74 +1,74 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.CommandsNext.Attributes;
/// <summary>
/// Marks this method as a command.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public sealed class CommandAttribute : Attribute
{
/// <summary>
/// Gets the name of this command.
/// </summary>
public string Name { get; }
/// <summary>
/// Marks this method as a command, using the method's name as command name.
/// </summary>
public CommandAttribute()
{
this.Name = null;
}
/// <summary>
/// Marks this method as a command with specified name.
/// </summary>
/// <param name="name">Name of this command.</param>
public CommandAttribute(string name)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullException(nameof(name), "Command names cannot be null, empty, or all-whitespace.");
if (name.Any(xc => char.IsWhiteSpace(xc)))
throw new ArgumentException("Command names cannot contain whitespace characters.", nameof(name));
this.Name = name;
}
}
/// <summary>
/// Marks this method as a group command.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public sealed class GroupCommandAttribute : Attribute
{
/// <summary>
/// Marks this method as a group command.
/// </summary>
public GroupCommandAttribute()
{ }
}
diff --git a/DisCatSharp.CommandsNext/Attributes/CooldownAttribute.cs b/DisCatSharp.CommandsNext/Attributes/CooldownAttribute.cs
index 5c8367bce..5ffaa467a 100644
--- a/DisCatSharp.CommandsNext/Attributes/CooldownAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/CooldownAttribute.cs
@@ -1,332 +1,332 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Defines a cooldown for this command. This allows you to define how many times can users execute a specific command
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public sealed class CooldownAttribute : CheckBaseAttribute
{
/// <summary>
/// Gets the maximum number of uses before this command triggers a cooldown for its bucket.
/// </summary>
public int MaxUses { get; }
/// <summary>
/// Gets the time after which the cooldown is reset.
/// </summary>
public TimeSpan Reset { get; }
/// <summary>
/// Gets the type of the cooldown bucket. This determines how cooldowns are applied.
/// </summary>
public CooldownBucketType BucketType { get; }
/// <summary>
/// Gets the cooldown buckets for this command.
/// </summary>
private readonly ConcurrentDictionary<string, CommandCooldownBucket> _buckets;
/// <summary>
/// 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.
/// </summary>
/// <param name="maxUses">Number of times the command can be used before triggering a cooldown.</param>
/// <param name="resetAfter">Number of seconds after which the cooldown is reset.</param>
/// <param name="bucketType">Type of cooldown bucket. This allows controlling whether the bucket will be cooled down per user, guild, channel, or globally.</param>
public CooldownAttribute(int maxUses, double resetAfter, CooldownBucketType bucketType)
{
this.MaxUses = maxUses;
this.Reset = TimeSpan.FromSeconds(resetAfter);
this.BucketType = bucketType;
this._buckets = new ConcurrentDictionary<string, CommandCooldownBucket>();
}
/// <summary>
/// Gets a cooldown bucket for given command context.
/// </summary>
/// <param name="ctx">Command context to get cooldown bucket for.</param>
/// <returns>Requested cooldown bucket, or null if one wasn't present.</returns>
public CommandCooldownBucket GetBucket(CommandContext ctx)
{
var bid = this.GetBucketId(ctx, out _, out _, out _);
this._buckets.TryGetValue(bid, out var bucket);
return bucket;
}
/// <summary>
/// Calculates the cooldown remaining for given command context.
/// </summary>
/// <param name="ctx">Context for which to calculate the cooldown.</param>
/// <returns>Remaining cooldown, or zero if no cooldown is active.</returns>
public TimeSpan GetRemainingCooldown(CommandContext ctx)
{
var bucket = this.GetBucket(ctx);
return bucket == null ? TimeSpan.Zero : bucket.RemainingUses > 0 ? TimeSpan.Zero : bucket.ResetsAt - DateTimeOffset.UtcNow;
}
/// <summary>
/// Calculates bucket ID for given command context.
/// </summary>
/// <param name="ctx">Context for which to calculate bucket ID for.</param>
/// <param name="userId">ID of the user with which this bucket is associated.</param>
/// <param name="channelId">ID of the channel with which this bucket is associated.</param>
/// <param name="guildId">ID of the guild with which this bucket is associated.</param>
/// <returns>Calculated bucket ID.</returns>
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;
}
/// <summary>
/// Executes a check.
/// </summary>
/// <param name="ctx">The command context.</param>
/// <param name="help">If true, help - returns true.</param>
public override async Task<bool> 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);
}
}
/// <summary>
/// Defines how are command cooldowns applied.
/// </summary>
public enum CooldownBucketType : int
{
/// <summary>
/// Denotes that the command will have its cooldown applied per-user.
/// </summary>
User = 1,
/// <summary>
/// Denotes that the command will have its cooldown applied per-channel.
/// </summary>
Channel = 2,
/// <summary>
/// Denotes that the command will have its cooldown applied per-guild. In DMs, this applies the cooldown per-channel.
/// </summary>
Guild = 4,
/// <summary>
/// Denotes that the command will have its cooldown applied globally.
/// </summary>
Global = 0
}
/// <summary>
/// Represents a cooldown bucket for commands.
/// </summary>
public sealed class CommandCooldownBucket : IEquatable<CommandCooldownBucket>
{
/// <summary>
/// Gets the ID of the user with whom this cooldown is associated.
/// </summary>
public ulong UserId { get; }
/// <summary>
/// Gets the ID of the channel with which this cooldown is associated.
/// </summary>
public ulong ChannelId { get; }
/// <summary>
/// Gets the ID of the guild with which this cooldown is associated.
/// </summary>
public ulong GuildId { get; }
/// <summary>
/// Gets the ID of the bucket. This is used to distinguish between cooldown buckets.
/// </summary>
public string BucketId { get; }
/// <summary>
/// Gets the remaining number of uses before the cooldown is triggered.
/// </summary>
public int RemainingUses
=> Volatile.Read(ref this._remainingUses);
private int _remainingUses;
/// <summary>
/// Gets the maximum number of times this command can be used in given timespan.
/// </summary>
public int MaxUses { get; }
/// <summary>
/// Gets the date and time at which the cooldown resets.
/// </summary>
public DateTimeOffset ResetsAt { get; internal set; }
/// <summary>
/// Gets the time after which this cooldown resets.
/// </summary>
public TimeSpan Reset { get; internal set; }
/// <summary>
/// Gets the semaphore used to lock the use value.
/// </summary>
private readonly SemaphoreSlim _usageSemaphore;
/// <summary>
/// Creates a new command cooldown bucket.
/// </summary>
/// <param name="maxUses">Maximum number of uses for this bucket.</param>
/// <param name="resetAfter">Time after which this bucket resets.</param>
/// <param name="userId">ID of the user with which this cooldown is associated.</param>
/// <param name="channelId">ID of the channel with which this cooldown is associated.</param>
/// <param name="guildId">ID of the guild with which this cooldown is associated.</param>
internal CommandCooldownBucket(int maxUses, TimeSpan resetAfter, ulong userId = 0, ulong channelId = 0, ulong guildId = 0)
{
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);
}
/// <summary>
/// Decrements the remaining use counter.
/// </summary>
/// <returns>Whether decrement succeeded or not.</returns>
internal async Task<bool> 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._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._remainingUses);
success = true;
}
// ...otherwise just fail
this._usageSemaphore.Release();
return success;
}
/// <summary>
/// Returns a string representation of this command cooldown bucket.
/// </summary>
/// <returns>String representation of this command cooldown bucket.</returns>
public override string ToString() => $"Command bucket {this.BucketId}";
/// <summary>
/// Checks whether this <see cref="CommandCooldownBucket"/> is equal to another object.
/// </summary>
/// <param name="obj">Object to compare to.</param>
/// <returns>Whether the object is equal to this <see cref="CommandCooldownBucket"/>.</returns>
public override bool Equals(object obj) => this.Equals(obj as CommandCooldownBucket);
/// <summary>
/// Checks whether this <see cref="CommandCooldownBucket"/> is equal to another <see cref="CommandCooldownBucket"/>.
/// </summary>
/// <param name="other"><see cref="CommandCooldownBucket"/> to compare to.</param>
/// <returns>Whether the <see cref="CommandCooldownBucket"/> is equal to this <see cref="CommandCooldownBucket"/>.</returns>
public bool Equals(CommandCooldownBucket other) => other is not null && (ReferenceEquals(this, other) || (this.UserId == other.UserId && this.ChannelId == other.ChannelId && this.GuildId == other.GuildId));
/// <summary>
/// Gets the hash code for this <see cref="CommandCooldownBucket"/>.
/// </summary>
/// <returns>The hash code for this <see cref="CommandCooldownBucket"/>.</returns>
public override int GetHashCode() => HashCode.Combine(this.UserId, this.ChannelId, this.GuildId);
/// <summary>
/// Gets whether the two <see cref="CommandCooldownBucket"/> objects are equal.
/// </summary>
/// <param name="bucket1">First bucket to compare.</param>
/// <param name="bucket2">Second bucket to compare.</param>
/// <returns>Whether the two buckets are equal.</returns>
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));
}
/// <summary>
/// Gets whether the two <see cref="CommandCooldownBucket"/> objects are not equal.
/// </summary>
/// <param name="bucket1">First bucket to compare.</param>
/// <param name="bucket2">Second bucket to compare.</param>
/// <returns>Whether the two buckets are not equal.</returns>
public static bool operator !=(CommandCooldownBucket bucket1, CommandCooldownBucket bucket2)
=> !(bucket1 == bucket2);
/// <summary>
/// Creates a bucket ID from given bucket parameters.
/// </summary>
/// <param name="userId">ID of the user with which this cooldown is associated.</param>
/// <param name="channelId">ID of the channel with which this cooldown is associated.</param>
/// <param name="guildId">ID of the guild with which this cooldown is associated.</param>
/// <returns>Generated bucket ID.</returns>
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/DescriptionAttribute.cs b/DisCatSharp.CommandsNext/Attributes/DescriptionAttribute.cs
index 4f90fa19c..d83e4b9d2 100644
--- a/DisCatSharp.CommandsNext/Attributes/DescriptionAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/DescriptionAttribute.cs
@@ -1,46 +1,46 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.CommandsNext.Attributes;
/// <summary>
/// Gives this command, group, or argument a description, which is used when listing help.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Parameter)]
public sealed class DescriptionAttribute : Attribute
{
/// <summary>
/// Gets the description for this command, group, or argument.
/// </summary>
public string Description { get; }
/// <summary>
/// Gives this command, group, or argument a description, which is used when listing help.
/// </summary>
/// <param name="description"></param>
public DescriptionAttribute(string description)
{
this.Description = description;
}
}
diff --git a/DisCatSharp.CommandsNext/Attributes/DontInjectAttribute.cs b/DisCatSharp.CommandsNext/Attributes/DontInjectAttribute.cs
index ad1c4c409..eb20dde52 100644
--- a/DisCatSharp.CommandsNext/Attributes/DontInjectAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/DontInjectAttribute.cs
@@ -1,32 +1,32 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.CommandsNext.Attributes;
/// <summary>
/// Prevents this field or property from having its value injected by dependency injection.
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class DontInjectAttribute : Attribute
{ }
diff --git a/DisCatSharp.CommandsNext/Attributes/GroupAttribute.cs b/DisCatSharp.CommandsNext/Attributes/GroupAttribute.cs
index 171f3bd12..abc21bfb6 100644
--- a/DisCatSharp.CommandsNext/Attributes/GroupAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/GroupAttribute.cs
@@ -1,61 +1,61 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.CommandsNext.Attributes;
/// <summary>
/// Marks this class as a command group.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public sealed class GroupAttribute : Attribute
{
/// <summary>
/// Gets the name of this group.
/// </summary>
public string Name { get; }
/// <summary>
/// Marks this class as a command group, using the class' name as group name.
/// </summary>
public GroupAttribute()
{
this.Name = null;
}
/// <summary>
/// Marks this class as a command group with specified name.
/// </summary>
/// <param name="name">Name of this group.</param>
public GroupAttribute(string name)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullException(nameof(name), "Group names cannot be null, empty, or all-whitespace.");
if (name.Any(xc => char.IsWhiteSpace(xc)))
throw new ArgumentException("Group names cannot contain whitespace characters.", nameof(name));
this.Name = name;
}
}
diff --git a/DisCatSharp.CommandsNext/Attributes/HiddenAttribute.cs b/DisCatSharp.CommandsNext/Attributes/HiddenAttribute.cs
index 7d87125a3..b2da3fa5b 100644
--- a/DisCatSharp.CommandsNext/Attributes/HiddenAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/HiddenAttribute.cs
@@ -1,32 +1,32 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.CommandsNext.Attributes;
/// <summary>
/// Marks this command or group as hidden.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public sealed class HiddenAttribute : Attribute
{ }
diff --git a/DisCatSharp.CommandsNext/Attributes/ModuleLifespanAttribute.cs b/DisCatSharp.CommandsNext/Attributes/ModuleLifespanAttribute.cs
index 91479ca26..e0eb13e88 100644
--- a/DisCatSharp.CommandsNext/Attributes/ModuleLifespanAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/ModuleLifespanAttribute.cs
@@ -1,62 +1,62 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.CommandsNext.Attributes;
/// <summary>
/// Defines a lifespan for this command module.
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class ModuleLifespanAttribute : Attribute
{
/// <summary>
/// Gets the lifespan defined for this module.
/// </summary>
public ModuleLifespan Lifespan { get; }
/// <summary>
/// Defines a lifespan for this command module.
/// </summary>
/// <param name="lifespan">Lifespan for this module.</param>
public ModuleLifespanAttribute(ModuleLifespan lifespan)
{
this.Lifespan = lifespan;
}
}
/// <summary>
/// Defines lifespan of a command module.
/// </summary>
public enum ModuleLifespan : int
{
/// <summary>
/// Defines that this module will be instantiated once.
/// </summary>
Singleton = 0,
/// <summary>
/// Defines that this module will be instantiated every time a containing command is called.
/// </summary>
Transient = 1
}
diff --git a/DisCatSharp.CommandsNext/Attributes/PriorityAttribute.cs b/DisCatSharp.CommandsNext/Attributes/PriorityAttribute.cs
index 79add7b2a..a44bab7f5 100644
--- a/DisCatSharp.CommandsNext/Attributes/PriorityAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/PriorityAttribute.cs
@@ -1,46 +1,46 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.CommandsNext.Attributes;
/// <summary>
/// Defines this command overload's priority. This determines the order in which overloads will be attempted to be called. Commands will be attempted in order of priority, in descending order.
/// </summary>
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public sealed class PriorityAttribute : Attribute
{
/// <summary>
/// Gets the priority of this command overload.
/// </summary>
public int Priority { get; }
/// <summary>
/// Defines this command overload's priority. This determines the order in which overloads will be attempted to be called. Commands will be attempted in order of priority, in descending order.
/// </summary>
/// <param name="priority">Priority of this command overload.</param>
public PriorityAttribute(int priority)
{
this.Priority = priority;
}
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RemainingTextAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RemainingTextAttribute.cs
index 68abc779c..fcab85cd0 100644
--- a/DisCatSharp.CommandsNext/Attributes/RemainingTextAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RemainingTextAttribute.cs
@@ -1,32 +1,32 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.CommandsNext.Attributes;
/// <summary>
/// Indicates that the command argument takes the rest of the input without parsing.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public class RemainingTextAttribute : Attribute
{ }
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireBoostingAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireBoostingAttribute.cs
index b42d9c355..a4f50571a 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireBoostingAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireBoostingAttribute.cs
@@ -1,83 +1,83 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Defines that usage of this command is restricted to boosters.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireBoostingAttribute : CheckBaseAttribute
{
/// <summary>
/// Gets the required boost time.
/// </summary>
public int Since { get; }
/// <summary>
/// Gets the required guild.
/// </summary>
public ulong GuildId { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RequireBoostingAttribute"/> class.
/// </summary>
/// <param name="days">Boosting since days.</param>
public RequireBoostingAttribute(int days = 0)
{
this.GuildId = 0;
this.Since = days;
}
/// <summary>
/// Initializes a new instance of the <see cref="RequireBoostingAttribute"/> class.
/// </summary>
/// <param name="guildId">Target guild id.</param>
/// <param name="days">Boosting since days.</param>
public RequireBoostingAttribute(ulong guildId, int days = 0)
{
this.GuildId = guildId;
this.Since = days;
}
/// <summary>
/// Executes the a check.
/// </summary>
/// <param name="ctx">The command context.</param>
/// <param name="help">If true, help - returns true.</param>
public override async Task<bool> 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/RequireBotPermissionsAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireBotPermissionsAttribute.cs
index f5149579b..17a13c233 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireBotPermissionsAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireBotPermissionsAttribute.cs
@@ -1,83 +1,83 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Threading.Tasks;
using DisCatSharp.Enums;
namespace DisCatSharp.CommandsNext.Attributes;
/// <summary>
/// Defines that usage of this command is only possible when the bot is granted a specific permission.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireBotPermissionsAttribute : CheckBaseAttribute
{
/// <summary>
/// Gets the permissions required by this attribute.
/// </summary>
public Permissions Permissions { get; }
/// <summary>
/// Gets or sets this check's behaviour in DMs. True means the check will always pass in DMs, whereas false means that it will always fail.
/// </summary>
public bool IgnoreDms { get; } = true;
/// <summary>
/// Defines that usage of this command is only possible when the bot is granted a specific permission.
/// </summary>
/// <param name="permissions">Permissions required to execute this command.</param>
/// <param name="ignoreDms">Sets this check's behaviour in DMs. True means the check will always pass in DMs, whereas false means that it will always fail.</param>
public RequireBotPermissionsAttribute(Permissions permissions, bool ignoreDms = true)
{
this.Permissions = permissions;
this.IgnoreDms = ignoreDms;
}
/// <summary>
/// Executes the a check.
/// </summary>
/// <param name="ctx">The command context.</param>
/// <param name="help">If true, help - returns true.</param>
public override async Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help)
{
if (ctx.Guild == null)
return this.IgnoreDms;
var bot = await ctx.Guild.GetMemberAsync(ctx.Client.CurrentUser.Id).ConfigureAwait(false);
if (bot == null)
return false;
if (bot.Id == ctx.Guild.OwnerId)
return true;
var channel = ctx.Channel;
if (ctx.Channel.GuildId == null)
{
channel = await ctx.Client.GetChannelAsync(ctx.Channel.Id, true);
}
var pbot = channel.PermissionsFor(bot);
return (pbot & Permissions.Administrator) != 0 || (pbot & this.Permissions) == this.Permissions;
}
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireCertifiedModeratorAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireCertifiedModeratorAttribute.cs
index fbd6b289e..35f214929 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireCertifiedModeratorAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireCertifiedModeratorAttribute.cs
@@ -1,40 +1,40 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Defines that usage of this command is restricted to discord certified moderators.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireCertifiedModeratorAttribute : CheckBaseAttribute
{
/// <summary>
/// Executes the a check.
/// </summary>
/// <param name="ctx">The command context.</param>
/// <param name="help">If true, help - returns true.</param>
public override Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help) => ctx.User.Flags.HasValue ? Task.FromResult(ctx.User.Flags.Value.HasFlag(UserFlags.CertifiedModerator)) : Task.FromResult(false);
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireCommunityAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireCommunityAttribute.cs
index 3255be825..5a0a0f76f 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireCommunityAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireCommunityAttribute.cs
@@ -1,47 +1,47 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Defines that a command is only usable within a community-enabled guild.
/// </summary>
///
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireCommunityAttribute : CheckBaseAttribute
{
/// <summary>
/// Defines that this command is only usable within a community-enabled guild.
/// </summary>
public RequireCommunityAttribute()
{ }
/// <summary>
/// Executes the a check.
/// </summary>
/// <param name="ctx">The command context.</param>
/// <param name="help">If true, help - returns true.</param>
public override Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help) => Task.FromResult(ctx.Guild != null && ctx.Guild.IsCommunity);
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireDirectMessageAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireDirectMessageAttribute.cs
index c2a328b3b..97c00a2de 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireDirectMessageAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireDirectMessageAttribute.cs
@@ -1,50 +1,50 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Threading.Tasks;
using DisCatSharp.Entities;
namespace DisCatSharp.CommandsNext.Attributes;
/// <summary>
/// Defines that a command is only usable within a direct message channel.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireDirectMessageAttribute : CheckBaseAttribute
{
/// <summary>
/// Defines that this command is only usable within a direct message channel.
/// </summary>
public RequireDirectMessageAttribute()
{ }
/// <summary>
/// Executes the a check.
/// </summary>
/// <param name="ctx">The command context.</param>
/// <param name="help">If true, help - returns true.</param>
public override Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help)
=> Task.FromResult(ctx.Channel is DiscordDmChannel);
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireDisCatSharpDeveloperAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireDisCatSharpDeveloperAttribute.cs
index 7bdf734ab..d53928ae8 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireDisCatSharpDeveloperAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireDisCatSharpDeveloperAttribute.cs
@@ -1,41 +1,41 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Defines that usage of this command is restricted to boosters.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireDisCatSharpDeveloperAttribute : CheckBaseAttribute
{
/// <summary>
/// Executes the a check.
/// </summary>
/// <param name="ctx">The command context.</param>
/// <param name="help">If true, help - returns true.</param>
public override Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help)
=> Task.FromResult(true);
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireGuildAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireGuildAttribute.cs
index 6ae30f3be..da9146b5c 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireGuildAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireGuildAttribute.cs
@@ -1,45 +1,45 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Threading.Tasks;
namespace DisCatSharp.CommandsNext.Attributes;
/// <summary>
/// Defines that a command is only usable within a guild.
/// </summary>
public sealed class RequireGuildAttribute : CheckBaseAttribute
{
/// <summary>
/// Defines that this command is only usable within a guild.
/// </summary>
public RequireGuildAttribute()
{ }
/// <summary>
/// Executes the a check.
/// </summary>
/// <param name="ctx">The command context.</param>
/// <param name="help">If true, help - returns true.</param>
public override Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help)
=> Task.FromResult(ctx.Guild != null);
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireGuildOwnerAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireGuildOwnerAttribute.cs
index df316031c..9c7743596 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireGuildOwnerAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireGuildOwnerAttribute.cs
@@ -1,53 +1,53 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Defines that usage of this command is restricted to the guild owner.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireGuildOwnerAttribute : CheckBaseAttribute
{
/// <summary>
/// Executes the a check.
/// </summary>
/// <param name="ctx">The command context.</param>
/// <param name="help">If true, help - returns true.</param>
public override async Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help)
{
var guild = await Task.FromResult(ctx.Guild != null);
if (guild)
{
var owner = await Task.FromResult(ctx.Member == ctx.Guild.Owner);
return owner;
}
else
{
return false;
}
}
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireMemberVerificationGateAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireMemberVerificationGateAttribute.cs
index 719c37271..49c265bf0 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireMemberVerificationGateAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireMemberVerificationGateAttribute.cs
@@ -1,44 +1,44 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Threading.Tasks;
namespace DisCatSharp.CommandsNext.Attributes;
/// <summary>
/// Defines that a command is only usable within a guild which has enabled the member verification gate.
/// </summary>
public sealed class RequireMemberVerificationGateAttribute : CheckBaseAttribute
{
/// <summary>
/// Defines that this command is only usable within guild which has enabled the member verification gate.
/// </summary>
public RequireMemberVerificationGateAttribute()
{ }
/// <summary>
/// Executes the a check.
/// </summary>
/// <param name="ctx">The command context.</param>
/// <param name="help">If true, help - returns true.</param>
public override Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help) => Task.FromResult(ctx.Guild != null && ctx.Guild.HasMemberVerificationGate);
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireNsfwAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireNsfwAttribute.cs
index e5aea0d2d..2e5bb224e 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireNsfwAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireNsfwAttribute.cs
@@ -1,41 +1,41 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Defines that usage of this command is restricted to NSFW channels.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireNsfwAttribute : CheckBaseAttribute
{
/// <summary>
/// Executes the a check.
/// </summary>
/// <param name="ctx">The command context.</param>
/// <param name="help">If true, help - returns true.</param>
public override Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help)
=> Task.FromResult(ctx.Channel.Guild == null || ctx.Channel.IsNsfw);
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireOwnerAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireOwnerAttribute.cs
index fb111092f..fd6d6231b 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireOwnerAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireOwnerAttribute.cs
@@ -1,47 +1,47 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
using System.Threading.Tasks;
namespace DisCatSharp.CommandsNext.Attributes;
/// <summary>
/// Defines that usage of this command is restricted to the owner of the bot.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireOwnerAttribute : CheckBaseAttribute
{
/// <summary>
/// Executes the a check.
/// </summary>
/// <param name="ctx">The command context.</param>
/// <param name="help">If true, help - returns true.</param>
public override Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help)
{
var app = ctx.Client.CurrentApplication;
var me = ctx.Client.CurrentUser;
return app != null ? Task.FromResult(app.Owners.Any(x => x.Id == ctx.User.Id)) : Task.FromResult(ctx.User.Id == me.Id);
}
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireOwnerOrIdAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireOwnerOrIdAttribute.cs
index 4834edcc7..e2b081423 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireOwnerOrIdAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireOwnerOrIdAttribute.cs
@@ -1,68 +1,68 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Requires ownership of the bot or a whitelisted id to execute this command.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireOwnerOrIdAttribute : CheckBaseAttribute
{
/// <summary>
/// Allowed user ids
/// </summary>
public IReadOnlyList<ulong> UserIds { get; }
/// <summary>
/// Defines that usage of this command is restricted to the owner or whitelisted ids of the bot.
/// </summary>
/// <param name="userIds">List of allowed user ids</param>
public RequireOwnerOrIdAttribute(params ulong[] userIds)
{
this.UserIds = new ReadOnlyCollection<ulong>(userIds);
}
/// <summary>
/// Executes the a check.
/// </summary>
/// <param name="ctx">The command context.</param>
/// <param name="help">If true, help - returns true.</param>
public override async Task<bool> 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/Attributes/RequirePermissionsAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequirePermissionsAttribute.cs
index c3ee201a2..e5eac535c 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequirePermissionsAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequirePermissionsAttribute.cs
@@ -1,94 +1,94 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Threading.Tasks;
using DisCatSharp.Enums;
namespace DisCatSharp.CommandsNext.Attributes;
/// <summary>
/// Defines that usage of this command is restricted to members with specified permissions. This check also verifies that the bot has the same permissions.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequirePermissionsAttribute : CheckBaseAttribute
{
/// <summary>
/// Gets the permissions required by this attribute.
/// </summary>
public Permissions Permissions { get; }
/// <summary>
/// Gets or sets this check's behaviour in DMs. True means the check will always pass in DMs, whereas false means that it will always fail.
/// </summary>
public bool IgnoreDms { get; } = true;
/// <summary>
/// Defines that usage of this command is restricted to members with specified permissions. This check also verifies that the bot has the same permissions.
/// </summary>
/// <param name="permissions">Permissions required to execute this command.</param>
/// <param name="ignoreDms">Sets this check's behaviour in DMs. True means the check will always pass in DMs, whereas false means that it will always fail.</param>
public RequirePermissionsAttribute(Permissions permissions, bool ignoreDms = true)
{
this.Permissions = permissions;
this.IgnoreDms = ignoreDms;
}
/// <summary>
/// Executes the a check.
/// </summary>
/// <param name="ctx">The command context.</param>
/// <param name="help">If true, help - returns true.</param>
public override async Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help)
{
if (ctx.Guild == null)
return this.IgnoreDms;
var channel = ctx.Channel;
if (ctx.Channel.GuildId == null)
{
channel = await ctx.Client.GetChannelAsync(ctx.Channel.Id, true);
}
var usr = ctx.Member;
if (usr == null)
return false;
var pusr = channel.PermissionsFor(usr);
var bot = await ctx.Guild.GetMemberAsync(ctx.Client.CurrentUser.Id).ConfigureAwait(false);
if (bot == null)
return false;
var pbot = channel.PermissionsFor(bot);
var usrok = ctx.Guild.OwnerId == usr.Id;
var botok = ctx.Guild.OwnerId == bot.Id;
if (!usrok)
usrok = (pusr & Permissions.Administrator) != 0 || (pusr & this.Permissions) == this.Permissions;
if (!botok)
botok = (pbot & Permissions.Administrator) != 0 || (pbot & this.Permissions) == this.Permissions;
return usrok && botok;
}
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequirePrefixesAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequirePrefixesAttribute.cs
index 105bc375d..30cb8dc33 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequirePrefixesAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequirePrefixesAttribute.cs
@@ -1,65 +1,65 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
using System.Threading.Tasks;
namespace DisCatSharp.CommandsNext.Attributes;
/// <summary>
/// Defines that usage of this command is only allowed with specific prefixes.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = false)]
public sealed class RequirePrefixesAttribute : CheckBaseAttribute
{
/// <summary>
/// Gets the array of prefixes with which execution of this command is allowed.
/// </summary>
public string[] Prefixes { get; }
/// <summary>
/// <para>Gets or sets default help behaviour for this check. When this is enabled, invoking help without matching prefix will show the commands.</para>
/// <para>Defaults to false.</para>
/// </summary>
public bool ShowInHelp { get; set; } = false;
/// <summary>
/// Defines that usage of this command is only allowed with specific prefixes.
/// </summary>
/// <param name="prefixes">Prefixes with which the execution of this command is allowed.</param>
public RequirePrefixesAttribute(params string[] prefixes)
{
if (prefixes?.Any() != true)
throw new ArgumentNullException(nameof(prefixes), "The allowed prefix collection cannot be null or empty.");
this.Prefixes = prefixes;
}
/// <summary>
/// Executes the a check.
/// </summary>
/// <param name="ctx">The command context.</param>
/// <param name="help">If true, help - returns true.</param>
public override Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help)
=> Task.FromResult((help && this.ShowInHelp) || this.Prefixes.Contains(ctx.Prefix, ctx.CommandsNext.GetStringComparer()));
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireReferencedMessageAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireReferencedMessageAttribute.cs
index eb46b0615..15eb210a7 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireReferencedMessageAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireReferencedMessageAttribute.cs
@@ -1,40 +1,40 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Threading.Tasks;
namespace DisCatSharp.CommandsNext.Attributes;
/// <summary>
/// Defines that a command is only usable when sent in reply. Command will appear in help regardless of this attribute.
/// </summary>
public sealed class RequireReferencedMessageAttribute : CheckBaseAttribute
{
/// <summary>
/// Defines that a command is only usable when sent in reply. Command will appear in help regardless of this attribute.
/// </summary>
public RequireReferencedMessageAttribute()
{ }
public override Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help)
=> Task.FromResult(help || ctx.Message.ReferencedMessage != null);
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireRolesAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireRolesAttribute.cs
index 35b44ebbf..704ac3655 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireRolesAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireRolesAttribute.cs
@@ -1,107 +1,107 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Defines that usage of this command is restricted to members with specified role. Note that it's much preferred to restrict access using <see cref="RequirePermissionsAttribute"/>.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireRolesAttribute : CheckBaseAttribute
{
/// <summary>
/// Gets the name of the role required to execute this command.
/// </summary>
public IReadOnlyList<string> RoleNames { get; }
/// <summary>
/// Gets the role checking mode. Refer to <see cref="RoleCheckMode"/> for more information.
/// </summary>
public RoleCheckMode CheckMode { get; }
/// <summary>
/// Defines that usage of this command is restricted to members with specified role. Note that it's much preferred to restrict access using <see cref="RequirePermissionsAttribute"/>.
/// </summary>
/// <param name="checkMode">Role checking mode.</param>
/// <param name="roleNames">Names of the role to be verified by this check.</param>
public RequireRolesAttribute(RoleCheckMode checkMode, params string[] roleNames)
{
this.CheckMode = checkMode;
this.RoleNames = new ReadOnlyCollection<string>(roleNames);
}
/// <summary>
/// Executes the a check.
/// </summary>
/// <param name="ctx">The command context.</param>
/// <param name="help">If true, help - returns true.</param>
public override Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help)
{
if (ctx.Guild == null || ctx.Member == null)
return Task.FromResult(false);
var rns = ctx.Member.Roles.Select(xr => xr.Name);
var rnc = rns.Count();
var ins = rns.Intersect(this.RoleNames, ctx.CommandsNext.GetStringComparer());
var inc = ins.Count();
return this.CheckMode switch
{
RoleCheckMode.All => Task.FromResult(this.RoleNames.Count == inc),
RoleCheckMode.SpecifiedOnly => Task.FromResult(rnc == inc),
RoleCheckMode.None => Task.FromResult(inc == 0),
_ => Task.FromResult(inc > 0),
};
}
}
/// <summary>
/// Specifies how does <see cref="RequireRolesAttribute"/> check for roles.
/// </summary>
public enum RoleCheckMode
{
/// <summary>
/// Member is required to have any of the specified roles.
/// </summary>
Any,
/// <summary>
/// Member is required to have all of the specified roles.
/// </summary>
All,
/// <summary>
/// Member is required to have exactly the same roles as specified; no extra roles may be present.
/// </summary>
SpecifiedOnly,
/// <summary>
/// Member is required to have none of the specified roles.
/// </summary>
None
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireStaffAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireStaffAttribute.cs
index 8d7a52204..4bfdacca5 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireStaffAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireStaffAttribute.cs
@@ -1,40 +1,40 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Defines that usage of this command is restricted to discord employees.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireStaffAttribute : CheckBaseAttribute
{
/// <summary>
/// Executes the a check.
/// </summary>
/// <param name="ctx">The command context.</param>
/// <param name="help">If true, help - returns true.</param>
public override Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help) => ctx.User.Flags.HasValue ? Task.FromResult(ctx.User.Flags.Value.HasFlag(UserFlags.Staff)) : Task.FromResult(false);
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireUserPermissionsAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireUserPermissionsAttribute.cs
index afd4d4ca4..442540ce0 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireUserPermissionsAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireUserPermissionsAttribute.cs
@@ -1,82 +1,82 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Threading.Tasks;
using DisCatSharp.Enums;
namespace DisCatSharp.CommandsNext.Attributes;
/// <summary>
/// Defines that usage of this command is restricted to members with specified permissions.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireUserPermissionsAttribute : CheckBaseAttribute
{
/// <summary>
/// Gets the permissions required by this attribute.
/// </summary>
public Permissions Permissions { get; }
/// <summary>
/// Gets or sets this check's behaviour in DMs. True means the check will always pass in DMs, whereas false means that it will always fail.
/// </summary>
public bool IgnoreDms { get; } = true;
/// <summary>
/// Defines that usage of this command is restricted to members with specified permissions.
/// </summary>
/// <param name="permissions">Permissions required to execute this command.</param>
/// <param name="ignoreDms">Sets this check's behaviour in DMs. True means the check will always pass in DMs, whereas false means that it will always fail.</param>
public RequireUserPermissionsAttribute(Permissions permissions, bool ignoreDms = true)
{
this.Permissions = permissions;
this.IgnoreDms = ignoreDms;
}
/// <summary>
/// Executes the a check.
/// </summary>
/// <param name="ctx">The command context.</param>
/// <param name="help">If true, help - returns true.</param>
public override Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help)
{
if (ctx.Guild == null)
return Task.FromResult(this.IgnoreDms);
var usr = ctx.Member;
if (usr == null)
return Task.FromResult(false);
if (usr.Id == ctx.Guild.OwnerId)
return Task.FromResult(true);
var pusr = ctx.Channel.PermissionsFor(usr);
if ((pusr & Permissions.Administrator) != 0)
return Task.FromResult(true);
return (pusr & this.Permissions) == this.Permissions ? Task.FromResult(true) : Task.FromResult(false);
}
}
diff --git a/DisCatSharp.CommandsNext/Attributes/RequireWelcomeScreenAttribute.cs b/DisCatSharp.CommandsNext/Attributes/RequireWelcomeScreenAttribute.cs
index 4f8632536..ac3ceebfd 100644
--- a/DisCatSharp.CommandsNext/Attributes/RequireWelcomeScreenAttribute.cs
+++ b/DisCatSharp.CommandsNext/Attributes/RequireWelcomeScreenAttribute.cs
@@ -1,46 +1,46 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Defines that a command is only usable within a guild which has enabled the welcome screen.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public sealed class RequireWelcomeScreenAttribute : CheckBaseAttribute
{
/// <summary>
/// Defines that this command is only usable within a guild which has enabled the welcome screen.
/// </summary>
public RequireWelcomeScreenAttribute()
{ }
/// <summary>
/// Executes a check.
/// </summary>
/// <param name="ctx">The command context.</param>
/// <param name="help">If true, help - returns true.</param>
public override Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help) => Task.FromResult(ctx.Guild != null && ctx.Guild.HasWelcomeScreen);
}
diff --git a/DisCatSharp.CommandsNext/BaseCommandModule.cs b/DisCatSharp.CommandsNext/BaseCommandModule.cs
index b8edf4662..6465ce13b 100644
--- a/DisCatSharp.CommandsNext/BaseCommandModule.cs
+++ b/DisCatSharp.CommandsNext/BaseCommandModule.cs
@@ -1,47 +1,47 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Threading.Tasks;
namespace DisCatSharp.CommandsNext;
/// <summary>
/// Represents a base class for all command modules.
/// </summary>
public abstract class BaseCommandModule
{
/// <summary>
/// Called before a command in the implementing module is executed.
/// </summary>
/// <param name="ctx">Context in which the method is being executed.</param>
/// <returns></returns>
public virtual Task BeforeExecutionAsync(CommandContext ctx)
=> Task.Delay(0);
/// <summary>
/// Called after a command in the implementing module is successfully executed.
/// </summary>
/// <param name="ctx">Context in which the method is being executed.</param>
/// <returns></returns>
public virtual Task AfterExecutionAsync(CommandContext ctx)
=> Task.Delay(0);
}
diff --git a/DisCatSharp.CommandsNext/CommandsNextConfiguration.cs b/DisCatSharp.CommandsNext/CommandsNextConfiguration.cs
index 65ecf44cf..5edba41fc 100644
--- a/DisCatSharp.CommandsNext/CommandsNextConfiguration.cs
+++ b/DisCatSharp.CommandsNext/CommandsNextConfiguration.cs
@@ -1,159 +1,159 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.CommandsNext.Attributes;
using DisCatSharp.Entities;
using Microsoft.Extensions.DependencyInjection;
namespace DisCatSharp.CommandsNext;
/// <summary>
/// <para>Represents a delegate for a function that takes a message, and returns the position of the start of command invocation in the message. It has to return -1 if prefix is not present.</para>
/// <para>
/// It is recommended that helper methods <see cref="CommandsNextUtilities.GetStringPrefixLength(DiscordMessage, string, StringComparison)"/> and <see cref="CommandsNextUtilities.GetMentionPrefixLength(DiscordMessage, DiscordUser)"/>
/// be used internally for checking. Their output can be passed through.
/// </para>
/// </summary>
/// <param name="msg">Message to check for prefix.</param>
/// <returns>Position of the command invocation or -1 if not present.</returns>
public delegate Task<int> PrefixResolverDelegate(DiscordMessage msg);
/// <summary>
/// Represents a configuration for <see cref="CommandsNextExtension"/>.
/// </summary>
public sealed class CommandsNextConfiguration
{
/// <summary>
/// <para>Sets the string prefixes used for commands.</para>
/// <para>Defaults to no value (disabled).</para>
/// </summary>
public List<string> StringPrefixes { internal get; set; }
/// <summary>
/// <para>Sets the custom prefix resolver used for commands.</para>
/// <para>Defaults to none (disabled).</para>
/// </summary>
public PrefixResolverDelegate PrefixResolver { internal get; set; }
/// <summary>
/// <para>Sets whether to allow mentioning the bot to be used as command prefix.</para>
/// <para>Defaults to true.</para>
/// </summary>
public bool EnableMentionPrefix { internal get; set; } = true;
/// <summary>
/// <para>Sets whether strings should be matched in a case-sensitive manner.</para>
/// <para>This switch affects the behaviour of default prefix resolver, command searching, and argument conversion.</para>
/// <para>Defaults to false.</para>
/// </summary>
public bool CaseSensitive { internal get; set; }
/// <summary>
/// <para>Sets whether to enable default help command.</para>
/// <para>Disabling this will allow you to make your own help command.</para>
/// <para>
/// Modifying default help can be achieved via custom help formatters (see <see cref="DisCatSharp.CommandsNext.Converters.BaseHelpFormatter"/> and <see cref="CommandsNextExtension.SetHelpFormatter{T}()"/> for more details).
/// It is recommended to use help formatter instead of disabling help.
/// </para>
/// <para>Defaults to true.</para>
/// </summary>
public bool EnableDefaultHelp { internal get; set; } = true;
/// <summary>
/// <para>Controls whether the default help will be sent via DMs or not.</para>
/// <para>Enabling this will make the bot respond with help via direct messages.</para>
/// <para>Defaults to false.</para>
/// </summary>
public bool DmHelp { internal get; set; }
/// <summary>
/// <para>Sets the default pre-execution checks for the built-in help command.</para>
/// <para>Only applicable if default help is enabled.</para>
/// <para>Defaults to null.</para>
/// </summary>
public List<CheckBaseAttribute> DefaultHelpChecks { internal get; set; }
/// <summary>
/// <para>Sets whether commands sent via direct messages should be processed.</para>
/// <para>Defaults to true.</para>
/// </summary>
public bool EnableDms { internal get; set; } = true;
/// <summary>
/// <para>Sets the service provider for this CommandsNext instance.</para>
/// <para>Objects in this provider are used when instantiating command modules. This allows passing data around without resorting to static members.</para>
/// <para>Defaults to an empty service provider.</para>
/// </summary>
public IServiceProvider ServiceProvider { internal get; set; }
/// <summary>
/// <para>Gets whether any extra arguments passed to commands should be ignored or not. If this is set to false, extra arguments will throw, otherwise they will be ignored.</para>
/// <para>Defaults to false.</para>
/// </summary>
public bool IgnoreExtraArguments { internal get; set; }
/// <summary>
/// <para>Gets or sets whether to automatically enable handling commands.</para>
/// <para>If this is set to false, you will need to manually handle each incoming message and pass it to CommandsNext.</para>
/// <para>Defaults to true.</para>
/// </summary>
public bool UseDefaultCommandHandler { internal get; set; } = true;
/// <summary>
/// Creates a new instance of <see cref="CommandsNextConfiguration"/>.
/// </summary>
public CommandsNextConfiguration() { }
/// <summary>
/// Initializes a new instance of the <see cref="CommandsNextConfiguration"/> class.
/// </summary>
/// <param name="provider">The service provider.</param>
[ActivatorUtilitiesConstructor]
public CommandsNextConfiguration(IServiceProvider provider)
{
this.ServiceProvider = provider;
}
/// <summary>
/// Creates a new instance of <see cref="CommandsNextConfiguration"/>, copying the properties of another configuration.
/// </summary>
/// <param name="other">Configuration the properties of which are to be copied.</param>
public CommandsNextConfiguration(CommandsNextConfiguration other)
{
this.CaseSensitive = other.CaseSensitive;
this.PrefixResolver = other.PrefixResolver;
this.DefaultHelpChecks = other.DefaultHelpChecks;
this.EnableDefaultHelp = other.EnableDefaultHelp;
this.EnableDms = other.EnableDms;
this.EnableMentionPrefix = other.EnableMentionPrefix;
this.IgnoreExtraArguments = other.IgnoreExtraArguments;
this.UseDefaultCommandHandler = other.UseDefaultCommandHandler;
this.ServiceProvider = other.ServiceProvider;
this.StringPrefixes = other.StringPrefixes;
this.DmHelp = other.DmHelp;
}
}
diff --git a/DisCatSharp.CommandsNext/CommandsNextEvents.cs b/DisCatSharp.CommandsNext/CommandsNextEvents.cs
index 485c137a2..5fc43ad67 100644
--- a/DisCatSharp.CommandsNext/CommandsNextEvents.cs
+++ b/DisCatSharp.CommandsNext/CommandsNextEvents.cs
@@ -1,41 +1,41 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 Microsoft.Extensions.Logging;
namespace DisCatSharp.CommandsNext;
/// <summary>
/// Contains well-defined event IDs used by CommandsNext.
/// </summary>
public static class CommandsNextEvents
{
/// <summary>
/// Miscellaneous events, that do not fit in any other category.
/// </summary>
internal static EventId Misc { get; } = new(200, "CommandsNext");
/// <summary>
/// Events pertaining to Gateway Intents. Typically diagnostic information.
/// </summary>
internal static EventId Intents { get; } = new(201, nameof(Intents));
}
diff --git a/DisCatSharp.CommandsNext/CommandsNextExtension.cs b/DisCatSharp.CommandsNext/CommandsNextExtension.cs
index 717168ad7..9a03591a6 100644
--- a/DisCatSharp.CommandsNext/CommandsNextExtension.cs
+++ b/DisCatSharp.CommandsNext/CommandsNextExtension.cs
@@ -1,1086 +1,1086 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// This is the class which handles command registration, management, and execution.
/// </summary>
public class CommandsNextExtension : BaseExtension
{
/// <summary>
/// Gets the config.
/// </summary>
private readonly CommandsNextConfiguration _config;
/// <summary>
/// Gets the help formatter.
/// </summary>
private readonly HelpFormatterFactory _helpFormatter;
/// <summary>
/// Gets the convert generic.
/// </summary>
private readonly MethodInfo _convertGeneric;
/// <summary>
/// Gets the user friendly type names.
/// </summary>
private readonly Dictionary<Type, string> _userFriendlyTypeNames;
/// <summary>
/// Gets the argument converters.
/// </summary>
internal Dictionary<Type, IArgumentConverter> ArgumentConverters { get; }
/// <summary>
/// Gets the service provider this CommandsNext module was configured with.
/// </summary>
public IServiceProvider Services
=> this._config.ServiceProvider;
/// <summary>
/// Initializes a new instance of the <see cref="CommandsNextExtension"/> class.
/// </summary>
/// <param name="cfg">The cfg.</param>
internal CommandsNextExtension(CommandsNextConfiguration cfg)
{
this._config = new CommandsNextConfiguration(cfg);
this._topLevelCommands = new Dictionary<string, Command>();
this._registeredCommandsLazy = new Lazy<IReadOnlyDictionary<string, Command>>(() => new ReadOnlyDictionary<string, Command>(this._topLevelCommands));
this._helpFormatter = new HelpFormatterFactory();
this._helpFormatter.SetFormatterType<DefaultHelpFormatter>();
this.ArgumentConverters = new Dictionary<Type, IArgumentConverter>
{
[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<Type, string>()
{
[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"
};
foreach (var xt in this.ArgumentConverters.Keys.ToArray())
{
var xti = xt.GetTypeInfo();
if (!xti.IsValueType)
continue;
var xcvt = typeof(NullableConverter<>).MakeGenericType(xt);
var xnt = typeof(Nullable<>).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 = this.GetType();
var ms = t.GetTypeInfo().DeclaredMethods;
var m = ms.FirstOrDefault(xm => xm.Name == "ConvertArgumentToObj" && xm.ContainsGenericParameters && !xm.IsStatic && xm.IsPrivate);
this._convertGeneric = m;
}
/// <summary>
/// Sets the help formatter to use with the default help command.
/// </summary>
/// <typeparam name="T">Type of the formatter to use.</typeparam>
public void SetHelpFormatter<T>() where T : BaseHelpFormatter => this._helpFormatter.SetFormatterType<T>();
#region DiscordClient Registration
/// <summary>
/// DO NOT USE THIS MANUALLY.
/// </summary>
/// <param name="client">DO NOT USE THIS MANUALLY.</param>
/// <exception cref="InvalidOperationException"/>
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<CommandsNextExtension, CommandExecutionEventArgs>("COMMAND_EXECUTED", TimeSpan.Zero, this.Client.EventErrorHandler);
this._error = new AsyncEvent<CommandsNextExtension, CommandErrorEventArgs>("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();
foreach (var cb in tcmds)
cb.WithExecutionChecks(checks);
}
if (tcmds != null)
foreach (var xc in tcmds)
this.AddToCommandDictionary(xc.Build(null));
}
}
#endregion
#region Command Handling
/// <summary>
/// Handles the commands async.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The e.</param>
/// <returns>A Task.</returns>
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));
}
/// <summary>
/// Finds a specified command by its qualified name, then separates arguments.
/// </summary>
/// <param name="commandString">Qualified name of the command, optionally with arguments.</param>
/// <param name="rawArguments">Separated arguments.</param>
/// <returns>Found command or null if none was found.</returns>
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;
}
/// <summary>
/// Creates a command execution context from specified arguments.
/// </summary>
/// <param name="msg">Message to use for context.</param>
/// <param name="prefix">Command prefix, used to execute commands.</param>
/// <param name="cmd">Command to execute.</param>
/// <param name="rawArguments">Raw arguments to pass to command.</param>
/// <returns>Created command execution context.</returns>
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;
}
/// <summary>
/// Executes specified command from given context.
/// </summary>
/// <param name="ctx">Context to execute command from.</param>
/// <returns></returns>
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();
}
}
/// <summary>
/// Runs the all checks async.
/// </summary>
/// <param name="cmd">The cmd.</param>
/// <param name="ctx">The ctx.</param>
/// <returns>A Task.</returns>
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
/// <summary>
/// Gets a dictionary of registered top-level commands.
/// </summary>
public IReadOnlyDictionary<string, Command> RegisteredCommands
=> this._registeredCommandsLazy.Value;
/// <summary>
/// Gets or sets the top level commands.
/// </summary>
private readonly Dictionary<string, Command> _topLevelCommands;
private readonly Lazy<IReadOnlyDictionary<string, Command>> _registeredCommandsLazy;
/// <summary>
/// Registers all commands from a given assembly. The command classes need to be public to be considered for registration.
/// </summary>
/// <param name="assembly">Assembly to register commands from.</param>
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);
}
/// <summary>
/// Registers all commands from a given command class.
/// </summary>
/// <typeparam name="T">Class which holds commands to register.</typeparam>
public void RegisterCommands<T>() where T : BaseCommandModule
{
var t = typeof(T);
this.RegisterCommands(t);
}
/// <summary>
/// Registers all commands from a given command class.
/// </summary>
/// <param name="t">Type of the class which holds commands to register.</param>
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));
}
/// <summary>
/// Registers the commands.
/// </summary>
/// <param name="t">The type.</param>
/// <param name="currentParent">The current parent.</param>
/// <param name="inheritedChecks">The inherited checks.</param>
/// <param name="foundCommands">The found commands.</param>
private void RegisterCommands(Type t, CommandGroupBuilder currentParent, IEnumerable<CheckBaseAttribute> inheritedChecks, out List<CommandBuilder> foundCommands)
{
var ti = t.GetTypeInfo();
var lifespan = ti.GetCustomAttribute<ModuleLifespanAttribute>();
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<CheckBaseAttribute>();
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<GroupCommandAttribute>() != 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<CommandBuilder>();
var commandBuilders = new Dictionary<string, CommandBuilder>();
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;
}
/// <summary>
/// Builds and registers all supplied commands.
/// </summary>
/// <param name="cmds">Commands to build and register.</param>
public void RegisterCommands(params CommandBuilder[] cmds)
{
foreach (var cmd in cmds)
this.AddToCommandDictionary(cmd.Build(null));
}
/// <summary>
/// Unregister specified commands from CommandsNext.
/// </summary>
/// <param name="cmds">Commands to unregister.</param>
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);
}
/// <summary>
/// Adds the to command dictionary.
/// </summary>
/// <param name="cmd">The cmd.</param>
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
/// <summary>
/// Represents the default help module.
/// </summary>
[ModuleLifespan(ModuleLifespan.Transient)]
public class DefaultHelpModule : BaseCommandModule
{
/// <summary>
/// Defaults the help async.
/// </summary>
/// <param name="ctx">The ctx.</param>
/// <param name="command">The command.</param>
/// <returns>A Task.</returns>
[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<Command>();
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<Command>();
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
/// <summary>
/// Creates a fake command context to execute commands with.
/// </summary>
/// <param name="actor">The user or member to use as message author.</param>
/// <param name="channel">The channel the message is supposed to appear from.</param>
/// <param name="messageContents">Contents of the message.</param>
/// <param name="prefix">Command prefix, used to execute commands.</param>
/// <param name="cmd">Command to execute.</param>
/// <param name="rawArguments">Raw arguments to pass to command.</param>
/// <returns>Created fake context.</returns>
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,
AttachmentsInternal = new List<DiscordAttachment>(),
EmbedsInternal = new List<DiscordEmbed>(),
TimestampRaw = now.ToString("yyyy-MM-ddTHH:mm:sszzz"),
ReactionsInternal = new List<DiscordReaction>()
};
var mentionedUsers = new List<DiscordUser>();
var mentionedRoles = msg.Channel.Guild != null ? new List<DiscordRole>() : null;
var mentionedChannels = msg.Channel.Guild != null ? new List<DiscordChannel>() : null;
if (!string.IsNullOrWhiteSpace(msg.Content))
{
if (msg.Channel.Guild != null)
{
mentionedUsers = Utilities.GetUserMentions(msg).Select(xid => msg.Channel.Guild.MembersInternal.TryGetValue(xid, out var member) ? member : null).Cast<DiscordUser>().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.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
/// <summary>
/// Converts a string to specified type.
/// </summary>
/// <typeparam name="T">Type to convert to.</typeparam>
/// <param name="value">Value to convert.</param>
/// <param name="ctx">Context in which to convert to.</param>
/// <returns>Converted object.</returns>
public async Task<T> ConvertArgument<T>(string value, CommandContext ctx)
{
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<T> 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;
}
/// <summary>
/// Converts a string to specified type.
/// </summary>
/// <param name="value">Value to convert.</param>
/// <param name="ctx">Context in which to convert to.</param>
/// <param name="type">Type to convert to.</param>
/// <returns>Converted object.</returns>
public async Task<object> ConvertArgument(string value, CommandContext ctx, Type type)
{
var m = this._convertGeneric.MakeGenericMethod(type);
try
{
return await (m.Invoke(this, new object[] { value, ctx }) as Task<object>).ConfigureAwait(false);
}
catch (TargetInvocationException ex)
{
throw ex.InnerException;
}
}
/// <summary>
/// Registers an argument converter for specified type.
/// </summary>
/// <typeparam name="T">Type for which to register the converter.</typeparam>
/// <param name="converter">Converter to register.</param>
public void RegisterConverter<T>(IArgumentConverter<T> 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;
}
/// <summary>
/// Unregister an argument converter for specified type.
/// </summary>
/// <typeparam name="T">Type for which to unregister the converter.</typeparam>
public void UnregisterConverter<T>()
{
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);
}
/// <summary>
/// Registers a user-friendly type name.
/// </summary>
/// <typeparam name="T">Type to register the name for.</typeparam>
/// <param name="value">Name to register.</param>
public void RegisterUserFriendlyTypeName<T>(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;
}
/// <summary>
/// Converts a type into user-friendly type name.
/// </summary>
/// <param name="t">Type to convert.</param>
/// <returns>User-friendly type name.</returns>
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
/// <summary>
/// Allows easier interoperability with reflection by turning the <see cref="Task{T}"/> returned by <see cref="ConvertArgument"/>
/// into a task containing <see cref="object"/>, using the provided generic type information.
/// </summary>
private async Task<object> ConvertArgumentToObj<T>(string value, CommandContext ctx)
=> await this.ConvertArgument<T>(value, ctx).ConfigureAwait(false);
/// <summary>
/// Gets the configuration-specific string comparer. This returns <see cref="StringComparer.Ordinal"/> or <see cref="StringComparer.OrdinalIgnoreCase"/>,
/// depending on whether <see cref="CommandsNextConfiguration.CaseSensitive"/> is set to <see langword="true"/> or <see langword="false"/>.
/// </summary>
/// <returns>A string comparer.</returns>
internal IEqualityComparer<string> GetStringComparer()
=> this._config.CaseSensitive
? StringComparer.Ordinal
: StringComparer.OrdinalIgnoreCase;
#endregion
#region Events
/// <summary>
/// Triggered whenever a command executes successfully.
/// </summary>
public event AsyncEventHandler<CommandsNextExtension, CommandExecutionEventArgs> CommandExecuted
{
add => this._executed.Register(value);
remove => this._executed.Unregister(value);
}
private AsyncEvent<CommandsNextExtension, CommandExecutionEventArgs> _executed;
/// <summary>
/// Triggered whenever a command throws an exception during execution.
/// </summary>
public event AsyncEventHandler<CommandsNextExtension, CommandErrorEventArgs> CommandErrored
{
add => this._error.Register(value);
remove => this._error.Unregister(value);
}
private AsyncEvent<CommandsNextExtension, CommandErrorEventArgs> _error;
/// <summary>
/// Fires when a command gets executed.
/// </summary>
/// <param name="e">The command execution event arguments.</param>
private Task OnCommandExecuted(CommandExecutionEventArgs e)
=> this._executed.InvokeAsync(this, e);
/// <summary>
/// Fires when a command fails.
/// </summary>
/// <param name="e">The command error event arguments.</param>
private Task OnCommandErrored(CommandErrorEventArgs e)
=> this._error.InvokeAsync(this, e);
#endregion
}
diff --git a/DisCatSharp.CommandsNext/CommandsNextUtilities.cs b/DisCatSharp.CommandsNext/CommandsNextUtilities.cs
index 313d7effd..06da1ecb1 100644
--- a/DisCatSharp.CommandsNext/CommandsNextUtilities.cs
+++ b/DisCatSharp.CommandsNext/CommandsNextUtilities.cs
@@ -1,430 +1,430 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Various CommandsNext-related utilities.
/// </summary>
public static class CommandsNextUtilities
{
/// <summary>
/// Gets the user regex.
/// </summary>
private static Regex s_userRegex { get; } = DiscordRegEx.User;
/// <summary>
/// Checks whether the message has a specified string prefix.
/// </summary>
/// <param name="msg">Message to check.</param>
/// <param name="str">String to check for.</param>
/// <param name="comparisonType">Method of string comparison for the purposes of finding prefixes.</param>
/// <returns>Positive number if the prefix is present, -1 otherwise.</returns>
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;
}
/// <summary>
/// Checks whether the message contains a specified mention prefix.
/// </summary>
/// <param name="msg">Message to check.</param>
/// <param name="user">User to check for.</param>
/// <returns>Positive number if the prefix is present, -1 otherwise.</returns>
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 + 1)
return -1;
var cnp = content[..(cni + 1)];
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)
/// <summary>
/// Extracts the next argument.
/// </summary>
/// <param name="str">The string.</param>
/// <param name="startPos">The start position.</param>
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<int>(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;
}
/// <summary>
/// Cleanups the string.
/// </summary>
/// <param name="s">The string.</param>
/// <param name="indices">The indices.</param>
internal static string CleanupString(this string s, IList<int> 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);
}
/// <summary>
/// Binds the arguments.
/// </summary>
/// <param name="ctx">The command context.</param>
/// <param name="ignoreSurplus">If true, ignore further text in string.</param>
internal static async Task<ArgumentBindingResult> BindArguments(CommandContext ctx, bool ignoreSurplus)
{
var command = ctx.Command;
var overload = ctx.Overload;
var args = new object[overload.Arguments.Count + 2];
args[1] = ctx;
var rawArgumentList = new List<string>(overload.Arguments.Count);
var argString = ctx.RawArgumentString;
var foundAt = 0;
foreach (var arg in overload.Arguments)
{
string argValue;
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);
}
/// <summary>
/// Whether this module is a candidate type.
/// </summary>
/// <param name="type">The type.</param>
internal static bool IsModuleCandidateType(this Type type)
=> type.GetTypeInfo().IsModuleCandidateType();
/// <summary>
/// Whether this module is a candidate type.
/// </summary>
/// <param name="ti">The type info.</param>
internal static bool IsModuleCandidateType(this TypeInfo ti)
{
// check if compiler-generated
if (ti.GetCustomAttribute<CompilerGeneratedAttribute>(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());
}
/// <summary>
/// Whether this is a command candidate.
/// </summary>
/// <param name="method">The method.</param>
/// <param name="parameters">The parameters.</param>
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;
}
/// <summary>
/// Creates the instance.
/// </summary>
/// <param name="t">The type.</param>
/// <param name="services">The services provider.</param>
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<DontInjectAttribute>() != 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<DontInjectAttribute>() != null)
continue;
var service = services.GetService(field.FieldType);
if (service == null)
continue;
field.SetValue(moduleInstance, service);
}
return moduleInstance;
}
}
diff --git a/DisCatSharp.CommandsNext/Converters/ArgumentBindingResult.cs b/DisCatSharp.CommandsNext/Converters/ArgumentBindingResult.cs
index cfbec2be1..1c959dbde 100644
--- a/DisCatSharp.CommandsNext/Converters/ArgumentBindingResult.cs
+++ b/DisCatSharp.CommandsNext/Converters/ArgumentBindingResult.cs
@@ -1,74 +1,74 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.CommandsNext.Converters;
/// <summary>
/// Represents a argument binding result.
/// </summary>
public readonly struct ArgumentBindingResult
{
/// <summary>
/// Gets a value indicating whether the binding is successful.
/// </summary>
public bool IsSuccessful { get; }
/// <summary>
/// Gets the converted.
/// </summary>
public object[] Converted { get; }
/// <summary>
/// Gets the raw.
/// </summary>
public IReadOnlyList<string> Raw { get; }
/// <summary>
/// Gets the reason.
/// </summary>
public Exception Reason { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ArgumentBindingResult"/> class.
/// </summary>
/// <param name="converted">The converted.</param>
/// <param name="raw">The raw.</param>
public ArgumentBindingResult(object[] converted, IReadOnlyList<string> raw)
{
this.IsSuccessful = true;
this.Reason = null;
this.Converted = converted;
this.Raw = raw;
}
/// <summary>
/// Initializes a new instance of the <see cref="ArgumentBindingResult"/> class.
/// </summary>
/// <param name="ex">The ex.</param>
public ArgumentBindingResult(Exception ex)
{
this.IsSuccessful = false;
this.Reason = ex;
this.Converted = null;
this.Raw = null;
}
}
diff --git a/DisCatSharp.CommandsNext/Converters/BaseHelpFormatter.cs b/DisCatSharp.CommandsNext/Converters/BaseHelpFormatter.cs
index 43406f345..65fdda256 100644
--- a/DisCatSharp.CommandsNext/Converters/BaseHelpFormatter.cs
+++ b/DisCatSharp.CommandsNext/Converters/BaseHelpFormatter.cs
@@ -1,72 +1,72 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.CommandsNext.Entities;
namespace DisCatSharp.CommandsNext.Converters;
/// <summary>
/// Represents a base class for all default help formatters.
/// </summary>
public abstract class BaseHelpFormatter
{
/// <summary>
/// Gets the context in which this formatter is being invoked.
/// </summary>
protected CommandContext Context { get; }
/// <summary>
/// Gets the CommandsNext extension which constructed this help formatter.
/// </summary>
protected CommandsNextExtension CommandsNext => this.Context.CommandsNext;
/// <summary>
/// Creates a new help formatter for specified CommandsNext extension instance.
/// </summary>
/// <param name="ctx">Context in which this formatter is being invoked.</param>
public BaseHelpFormatter(CommandContext ctx)
{
this.Context = ctx;
}
/// <summary>
/// Sets the command this help message will be for.
/// </summary>
/// <param name="command">Command for which the help message is being produced.</param>
/// <returns>This help formatter.</returns>
public abstract BaseHelpFormatter WithCommand(Command command);
/// <summary>
/// Sets the subcommands for this command, if applicable. This method will be called with filtered data.
/// </summary>
/// <param name="subcommands">Subcommands for this command group.</param>
/// <returns>This help formatter.</returns>
public abstract BaseHelpFormatter WithSubcommands(IEnumerable<Command> subcommands);
/// <summary>
/// Constructs the help message.
/// </summary>
/// <returns>Data for the help message.</returns>
public abstract CommandHelpMessage Build();
}
diff --git a/DisCatSharp.CommandsNext/Converters/DefaultHelpFormatter.cs b/DisCatSharp.CommandsNext/Converters/DefaultHelpFormatter.cs
index a015b6971..d03cc8e92 100644
--- a/DisCatSharp.CommandsNext/Converters/DefaultHelpFormatter.cs
+++ b/DisCatSharp.CommandsNext/Converters/DefaultHelpFormatter.cs
@@ -1,124 +1,124 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Text;
using DisCatSharp.CommandsNext.Entities;
using DisCatSharp.Entities;
namespace DisCatSharp.CommandsNext.Converters;
/// <summary>
/// Default CommandsNext help formatter.
/// </summary>
public class DefaultHelpFormatter : BaseHelpFormatter
{
/// <summary>
/// Gets the embed builder.
/// </summary>
public DiscordEmbedBuilder EmbedBuilder { get; }
/// <summary>
/// Gets or sets the command.
/// </summary>
private Command _command;
/// <summary>
/// Creates a new default help formatter.
/// </summary>
/// <param name="ctx">Context in which this formatter is being invoked.</param>
public DefaultHelpFormatter(CommandContext ctx)
: base(ctx)
{
this.EmbedBuilder = new DiscordEmbedBuilder()
.WithTitle("Help")
.WithColor(0x007FFF);
}
/// <summary>
/// Sets the command this help message will be for.
/// </summary>
/// <param name="command">Command for which the help message is being produced.</param>
/// <returns>This help formatter.</returns>
public override BaseHelpFormatter WithCommand(Command command)
{
this._command = command;
this.EmbedBuilder.WithDescription($"{Formatter.InlineCode(command.Name)}: {command.Description ?? "No description provided."}");
if (command is CommandGroup cgroup && cgroup.IsExecutableWithoutSubcommands)
this.EmbedBuilder.WithDescription($"{this.EmbedBuilder.Description}\n\nThis group can be executed as a standalone command.");
if (command.Aliases?.Any() == true)
this.EmbedBuilder.AddField(new DiscordEmbedField("Aliases", string.Join(", ", command.Aliases.Select(Formatter.InlineCode))));
if (command.Overloads?.Any() == true)
{
var sb = new StringBuilder();
foreach (var ovl in command.Overloads.OrderByDescending(x => x.Priority))
{
sb.Append('`').Append(command.QualifiedName);
foreach (var arg in ovl.Arguments)
sb.Append(arg.IsOptional || arg.IsCatchAll ? " [" : " <").Append(arg.Name).Append(arg.IsCatchAll ? "..." : "").Append(arg.IsOptional || arg.IsCatchAll ? ']' : '>');
sb.Append("`\n");
foreach (var arg in ovl.Arguments)
sb.Append('`').Append(arg.Name).Append(" (").Append(this.CommandsNext.GetUserFriendlyTypeName(arg.Type)).Append(")`: ").Append(arg.Description ?? "No description provided.").Append('\n');
sb.Append('\n');
}
this.EmbedBuilder.AddField(new DiscordEmbedField("Arguments", sb.ToString().Trim()));
}
return this;
}
/// <summary>
/// Sets the subcommands for this command, if applicable. This method will be called with filtered data.
/// </summary>
/// <param name="subcommands">Subcommands for this command group.</param>
/// <returns>This help formatter.</returns>
public override BaseHelpFormatter WithSubcommands(IEnumerable<Command> subcommands)
{
this.EmbedBuilder.AddField(new DiscordEmbedField(this._command != null ? "Subcommands" : "Commands", string.Join(", ", subcommands.Select(x => Formatter.InlineCode(x.Name)))));
return this;
}
/// <summary>
/// Construct the help message.
/// </summary>
/// <returns>Data for the help message.</returns>
public override CommandHelpMessage Build()
{
if (this._command == null)
this.EmbedBuilder.WithDescription("Listing all top-level commands and groups. Specify a command to see more information.");
return new CommandHelpMessage(embed: this.EmbedBuilder.Build());
}
}
diff --git a/DisCatSharp.CommandsNext/Converters/EntityConverters.cs b/DisCatSharp.CommandsNext/Converters/EntityConverters.cs
index 09872254d..8baf4c179 100644
--- a/DisCatSharp.CommandsNext/Converters/EntityConverters.cs
+++ b/DisCatSharp.CommandsNext/Converters/EntityConverters.cs
@@ -1,455 +1,455 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Common.RegularExpressions;
using DisCatSharp.Entities;
namespace DisCatSharp.CommandsNext.Converters;
/// <summary>
/// Represents a discord user converter.
/// </summary>
public class DiscordUserConverter : IArgumentConverter<DiscordUser>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
async Task<Optional<DiscordUser>> IArgumentConverter<DiscordUser>.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);
return Optional.FromNullable(result);
}
var m = DiscordRegEx.User.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);
return Optional.FromNullable(result);
}
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 Optional.FromNullable<DiscordUser>(usr);
}
}
/// <summary>
/// Represents a discord member converter.
/// </summary>
public class DiscordMemberConverter : IArgumentConverter<DiscordMember>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
async Task<Optional<DiscordMember>> IArgumentConverter<DiscordMember>.ConvertAsync(string value, CommandContext ctx)
{
if (ctx.Guild == null)
return Optional.None;
if (ulong.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var uid))
{
var result = await ctx.Guild.GetMemberAsync(uid).ConfigureAwait(false);
return Optional.FromNullable(result);
}
var m = DiscordRegEx.User.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);
return Optional.FromNullable(result);
}
var searchResult = await ctx.Guild.SearchMembersAsync(value).ConfigureAwait(false);
if (searchResult.Any())
return Optional.Some(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);
return Optional.FromNullable(us.FirstOrDefault());
}
}
/// <summary>
/// Represents a discord channel converter.
/// </summary>
public class DiscordChannelConverter : IArgumentConverter<DiscordChannel>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
async Task<Optional<DiscordChannel>> IArgumentConverter<DiscordChannel>.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);
return Optional.FromNullable(result);
}
var m = DiscordRegEx.Channel.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);
return Optional.FromNullable(result);
}
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 Optional.FromNullable(chn);
}
}
/// <summary>
/// Represents a discord thread channel converter.
/// </summary>
public class DiscordThreadChannelConverter : IArgumentConverter<DiscordThreadChannel>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
async Task<Optional<DiscordThreadChannel>> IArgumentConverter<DiscordThreadChannel>.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);
return Optional.FromNullable(result);
}
var m = DiscordRegEx.Channel.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);
return Optional.FromNullable(result);
}
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 Optional.FromNullable(tchn);
}
}
/// <summary>
/// Represents a discord role converter.
/// </summary>
public class DiscordRoleConverter : IArgumentConverter<DiscordRole>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
Task<Optional<DiscordRole>> IArgumentConverter<DiscordRole>.ConvertAsync(string value, CommandContext ctx)
{
if (ctx.Guild == null)
return Task.FromResult(Optional<DiscordRole>.None);
if (ulong.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var rid))
{
var result = ctx.Guild.GetRole(rid);
return Task.FromResult(Optional.FromNullable(result));
}
var m = DiscordRegEx.Role.Match(value);
if (m.Success && ulong.TryParse(m.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out rid))
{
var result = ctx.Guild.GetRole(rid);
return Task.FromResult(Optional.FromNullable(result));
}
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(Optional.FromNullable(rol));
}
}
/// <summary>
/// Represents a discord guild converter.
/// </summary>
public class DiscordGuildConverter : IArgumentConverter<DiscordGuild>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
Task<Optional<DiscordGuild>> IArgumentConverter<DiscordGuild>.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.Some(result))
: Task.FromResult(Optional<DiscordGuild>.None);
}
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(Optional.FromNullable(gld));
}
}
/// <summary>
/// Represents a discord invite converter.
/// </summary>
public class DiscordInviteConverter : IArgumentConverter<DiscordInvite>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
async Task<Optional<DiscordInvite>> IArgumentConverter<DiscordInvite>.ConvertAsync(string value, CommandContext ctx)
{
var m = DiscordRegEx.Invite.Match(value);
if (m.Success)
{
ulong? eventId = ulong.TryParse(m.Groups["event"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture,
out var eid) ? eid : null;
var result = await ctx.Client.GetInviteByCodeAsync(m.Groups["code"].Value, scheduledEventId: eventId).ConfigureAwait(false);
return Optional.FromNullable(result);
}
var inv = await ctx.Client.GetInviteByCodeAsync(value);
return Optional.FromNullable(inv);
}
}
/// <summary>
/// Represents a discord message converter.
/// </summary>
public class DiscordMessageConverter : IArgumentConverter<DiscordMessage>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
async Task<Optional<DiscordMessage>> IArgumentConverter<DiscordMessage>.ConvertAsync(string value, CommandContext ctx)
{
if (string.IsNullOrWhiteSpace(value))
return Optional.None;
var msguri = value.StartsWith("<") && value.EndsWith(">") ? value[1..^1] : value;
ulong mid;
if (Uri.TryCreate(msguri, UriKind.Absolute, out var uri))
{
var uripath = DiscordRegEx.MessageLink.Match(uri.AbsoluteUri);
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.None;
var chn = await ctx.Client.GetChannelAsync(cid).ConfigureAwait(false);
if (chn == null)
return Optional.None;
var msg = await chn.GetMessageAsync(mid).ConfigureAwait(false);
return Optional.FromNullable(msg);
}
if (ulong.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out mid))
{
var result = await ctx.Channel.GetMessageAsync(mid).ConfigureAwait(false);
return Optional.FromNullable(result);
}
return Optional.None;
}
}
/// <summary>
/// Represents a discord scheduled event converter.
/// </summary>
public class DiscordScheduledEventConverter : IArgumentConverter<DiscordScheduledEvent>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
async Task<Optional<DiscordScheduledEvent>> IArgumentConverter<DiscordScheduledEvent>.ConvertAsync(string value, CommandContext ctx)
{
if (string.IsNullOrWhiteSpace(value))
return Optional.None;
var msguri = value.StartsWith("<") && value.EndsWith(">") ? value[1..^1] : value;
ulong seid;
if (Uri.TryCreate(msguri, UriKind.Absolute, out var uri))
{
var uripath = DiscordRegEx.Event.Match(uri.AbsoluteUri);
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))
{
var guild = await ctx.Client.GetGuildAsync(gid).ConfigureAwait(false);
if (guild == null)
return Optional.None;
var ev = await guild.GetScheduledEventAsync(seid).ConfigureAwait(false);
return Optional.FromNullable(ev);
}
try
{
var invite = await ctx.CommandsNext.ConvertArgument<DiscordInvite>(value, ctx).ConfigureAwait(false);
return Optional.FromNullable(invite.GuildScheduledEvent);
}
catch
{
return Optional.None;
}
}
if (ulong.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out seid))
{
var result = await ctx.Guild.GetScheduledEventAsync(seid).ConfigureAwait(false);
return Optional.FromNullable(result);
}
return Optional.None;
}
}
/// <summary>
/// Represents a discord emoji converter.
/// </summary>
public class DiscordEmojiConverter : IArgumentConverter<DiscordEmoji>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
Task<Optional<DiscordEmoji>> IArgumentConverter<DiscordEmoji>.ConvertAsync(string value, CommandContext ctx)
{
if (DiscordEmoji.TryFromUnicode(ctx.Client, value, out var emoji))
{
var result = emoji;
return Task.FromResult(Optional.Some(result));
}
var m = DiscordRegEx.Emoji.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<DiscordEmoji>.None)
: DiscordEmoji.TryFromGuildEmote(ctx.Client, id, out emoji)
? Task.FromResult(Optional.Some(emoji))
: Task.FromResult(Optional.Some(new DiscordEmoji
{
Discord = ctx.Client,
Id = id,
Name = name,
IsAnimated = anim,
RequiresColons = true,
IsManaged = false
}));
}
return Task.FromResult(Optional<DiscordEmoji>.None);
}
}
/// <summary>
/// Represents a discord color converter.
/// </summary>
public class DiscordColorConverter : IArgumentConverter<DiscordColor>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
Task<Optional<DiscordColor>> IArgumentConverter<DiscordColor>.ConvertAsync(string value, CommandContext ctx)
{
var m = CommonRegEx.HexColorString.Match(value);
if (m.Success && int.TryParse(m.Groups[1].Value, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var clr))
return Task.FromResult(Optional.Some<DiscordColor>(clr));
m = CommonRegEx.RgbColorString.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<DiscordColor>.None)
: Task.FromResult(Optional.Some(new DiscordColor(r, g, b)));
}
return Task.FromResult(Optional<DiscordColor>.None);
}
}
diff --git a/DisCatSharp.CommandsNext/Converters/EnumConverter.cs b/DisCatSharp.CommandsNext/Converters/EnumConverter.cs
index b672ff88c..b9759427b 100644
--- a/DisCatSharp.CommandsNext/Converters/EnumConverter.cs
+++ b/DisCatSharp.CommandsNext/Converters/EnumConverter.cs
@@ -1,51 +1,51 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Reflection;
using System.Threading.Tasks;
using DisCatSharp.Entities;
namespace DisCatSharp.CommandsNext.Converters;
/// <summary>
/// Represents a enum converter.
/// </summary>
public class EnumConverter<T> : IArgumentConverter<T> where T : struct, IComparable, IConvertible, IFormattable
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
Task<Optional<T>> IArgumentConverter<T>.ConvertAsync(string value, CommandContext ctx)
{
var t = typeof(T);
var ti = t.GetTypeInfo();
return !ti.IsEnum
? throw new InvalidOperationException("Cannot convert non-enum value to an enum.")
: Enum.TryParse(value, !ctx.Config.CaseSensitive, out T ev)
? Task.FromResult(Optional.Some(ev))
: Task.FromResult(Optional<T>.None);
}
}
diff --git a/DisCatSharp.CommandsNext/Converters/HelpFormatterFactory.cs b/DisCatSharp.CommandsNext/Converters/HelpFormatterFactory.cs
index 543beec5e..ec6350689 100644
--- a/DisCatSharp.CommandsNext/Converters/HelpFormatterFactory.cs
+++ b/DisCatSharp.CommandsNext/Converters/HelpFormatterFactory.cs
@@ -1,52 +1,52 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 Microsoft.Extensions.DependencyInjection;
namespace DisCatSharp.CommandsNext.Converters;
/// <summary>
/// Represents the help formatter factory.
/// </summary>
internal class HelpFormatterFactory
{
/// <summary>
/// Gets or sets the factory.
/// </summary>
private ObjectFactory _factory;
/// <summary>
/// Initializes a new instance of the <see cref="HelpFormatterFactory"/> class.
/// </summary>
public HelpFormatterFactory() { }
/// <summary>
/// Sets the formatter type.
/// </summary>
public void SetFormatterType<T>() where T : BaseHelpFormatter => this._factory = ActivatorUtilities.CreateFactory(typeof(T), new[] { typeof(CommandContext) });
/// <summary>
/// Creates the help formatter.
/// </summary>
/// <param name="ctx">The command context.</param>
public BaseHelpFormatter Create(CommandContext ctx) => this._factory(ctx.Services, new object[] { ctx }) as BaseHelpFormatter;
}
diff --git a/DisCatSharp.CommandsNext/Converters/IArgumentConverter.cs b/DisCatSharp.CommandsNext/Converters/IArgumentConverter.cs
index 0c46f2dae..965689195 100644
--- a/DisCatSharp.CommandsNext/Converters/IArgumentConverter.cs
+++ b/DisCatSharp.CommandsNext/Converters/IArgumentConverter.cs
@@ -1,48 +1,48 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Threading.Tasks;
using DisCatSharp.Entities;
namespace DisCatSharp.CommandsNext.Converters;
/// <summary>
/// Argument converter abstract.
/// </summary>
public interface IArgumentConverter
{ }
/// <summary>
/// Represents a converter for specific argument type.
/// </summary>
/// <typeparam name="T">Type for which the converter is to be registered.</typeparam>
public interface IArgumentConverter<T> : IArgumentConverter
{
/// <summary>
/// Converts the raw value into the specified type.
/// </summary>
/// <param name="value">Value to convert.</param>
/// <param name="ctx">Context in which the value will be converted.</param>
/// <returns>A structure containing information whether the value was converted, and, if so, the converted value.</returns>
Task<Optional<T>> ConvertAsync(string value, CommandContext ctx);
}
diff --git a/DisCatSharp.CommandsNext/Converters/NullableConverter.cs b/DisCatSharp.CommandsNext/Converters/NullableConverter.cs
index 5a73b458d..b1668f57f 100644
--- a/DisCatSharp.CommandsNext/Converters/NullableConverter.cs
+++ b/DisCatSharp.CommandsNext/Converters/NullableConverter.cs
@@ -1,56 +1,56 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Threading.Tasks;
using DisCatSharp.Entities;
namespace DisCatSharp.CommandsNext.Converters;
/// <summary>
/// Represents a nullable converter.
/// </summary>
public class NullableConverter<T> : IArgumentConverter<T?> where T : struct
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
async Task<Optional<T?>> IArgumentConverter<T?>.ConvertAsync(string value, CommandContext ctx)
{
if (!ctx.Config.CaseSensitive)
value = value.ToLowerInvariant();
if (value == "null")
return null;
if (ctx.CommandsNext.ArgumentConverters.TryGetValue(typeof(T), out var cv))
{
var cvx = cv as IArgumentConverter<T>;
var val = await cvx.ConvertAsync(value, ctx).ConfigureAwait(false);
return val.Map<T?>(x => x);
}
return Optional.None;
}
}
diff --git a/DisCatSharp.CommandsNext/Converters/NumericConverters.cs b/DisCatSharp.CommandsNext/Converters/NumericConverters.cs
index 01f76d380..fa699d23d 100644
--- a/DisCatSharp.CommandsNext/Converters/NumericConverters.cs
+++ b/DisCatSharp.CommandsNext/Converters/NumericConverters.cs
@@ -1,220 +1,220 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 System.Threading.Tasks;
using DisCatSharp.Entities;
namespace DisCatSharp.CommandsNext.Converters;
/// <summary>
/// The bool converter.
/// </summary>
public class BoolConverter : IArgumentConverter<bool>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
Task<Optional<bool>> IArgumentConverter<bool>.ConvertAsync(string value, CommandContext ctx) =>
bool.TryParse(value, out var result)
? Task.FromResult(Optional.Some(result))
: Task.FromResult(Optional<bool>.None);
}
/// <summary>
/// The int8 converter.
/// </summary>
public class Int8Converter : IArgumentConverter<sbyte>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
Task<Optional<sbyte>> IArgumentConverter<sbyte>.ConvertAsync(string value, CommandContext ctx) =>
sbyte.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)
? Task.FromResult(Optional.Some(result))
: Task.FromResult(Optional<sbyte>.None);
}
/// <summary>
/// The uint8 converter.
/// </summary>
public class Uint8Converter : IArgumentConverter<byte>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
Task<Optional<byte>> IArgumentConverter<byte>.ConvertAsync(string value, CommandContext ctx) =>
byte.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)
? Task.FromResult(Optional.Some(result))
: Task.FromResult(Optional<byte>.None);
}
/// <summary>
/// The int16 converter.
/// </summary>
public class Int16Converter : IArgumentConverter<short>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
Task<Optional<short>> IArgumentConverter<short>.ConvertAsync(string value, CommandContext ctx) =>
short.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)
? Task.FromResult(Optional.Some(result))
: Task.FromResult(Optional<short>.None);
}
/// <summary>
/// The uint16 converter.
/// </summary>
public class Uint16Converter : IArgumentConverter<ushort>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
Task<Optional<ushort>> IArgumentConverter<ushort>.ConvertAsync(string value, CommandContext ctx) =>
ushort.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)
? Task.FromResult(Optional.Some(result))
: Task.FromResult(Optional<ushort>.None);
}
/// <summary>
/// The int32 converter.
/// </summary>
public class Int32Converter : IArgumentConverter<int>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
Task<Optional<int>> IArgumentConverter<int>.ConvertAsync(string value, CommandContext ctx) =>
int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)
? Task.FromResult(Optional.Some(result))
: Task.FromResult(Optional<int>.None);
}
/// <summary>
/// The uint32 converter.
/// </summary>
public class Uint32Converter : IArgumentConverter<uint>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
Task<Optional<uint>> IArgumentConverter<uint>.ConvertAsync(string value, CommandContext ctx) =>
uint.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)
? Task.FromResult(Optional.Some(result))
: Task.FromResult(Optional<uint>.None);
}
/// <summary>
/// The int64 converter.
/// </summary>
public class Int64Converter : IArgumentConverter<long>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
Task<Optional<long>> IArgumentConverter<long>.ConvertAsync(string value, CommandContext ctx) =>
long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)
? Task.FromResult(Optional.Some(result))
: Task.FromResult(Optional<long>.None);
}
/// <summary>
/// The uint64 converter.
/// </summary>
public class Uint64Converter : IArgumentConverter<ulong>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
Task<Optional<ulong>> IArgumentConverter<ulong>.ConvertAsync(string value, CommandContext ctx) =>
ulong.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)
? Task.FromResult(Optional.Some(result))
: Task.FromResult(Optional<ulong>.None);
}
/// <summary>
/// The float32 converter.
/// </summary>
public class Float32Converter : IArgumentConverter<float>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
Task<Optional<float>> IArgumentConverter<float>.ConvertAsync(string value, CommandContext ctx) =>
float.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out var result)
? Task.FromResult(Optional.Some(result))
: Task.FromResult(Optional<float>.None);
}
/// <summary>
/// The float64 converter.
/// </summary>
public class Float64Converter : IArgumentConverter<double>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
Task<Optional<double>> IArgumentConverter<double>.ConvertAsync(string value, CommandContext ctx) =>
double.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out var result)
? Task.FromResult(Optional.Some(result))
: Task.FromResult(Optional<double>.None);
}
/// <summary>
/// The float128 converter.
/// </summary>
public class Float128Converter : IArgumentConverter<decimal>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
Task<Optional<decimal>> IArgumentConverter<decimal>.ConvertAsync(string value, CommandContext ctx) =>
decimal.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out var result)
? Task.FromResult(Optional.Some(result))
: Task.FromResult(Optional<decimal>.None);
}
diff --git a/DisCatSharp.CommandsNext/Converters/StringConverter.cs b/DisCatSharp.CommandsNext/Converters/StringConverter.cs
index 3362cc8bc..d29ee723c 100644
--- a/DisCatSharp.CommandsNext/Converters/StringConverter.cs
+++ b/DisCatSharp.CommandsNext/Converters/StringConverter.cs
@@ -1,68 +1,68 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Threading.Tasks;
using DisCatSharp.Entities;
namespace DisCatSharp.CommandsNext.Converters;
/// <summary>
/// Represents a string converter.
/// </summary>
public class StringConverter : IArgumentConverter<string>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
Task<Optional<string>> IArgumentConverter<string>.ConvertAsync(string value, CommandContext ctx)
=> Task.FromResult(Optional.Some(value));
}
/// <summary>
/// Represents a uri converter.
/// </summary>
public class UriConverter : IArgumentConverter<Uri>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
Task<Optional<Uri>> IArgumentConverter<Uri>.ConvertAsync(string value, CommandContext ctx)
{
try
{
if (value.StartsWith("<") && value.EndsWith(">"))
value = value[1..^1];
return Task.FromResult(Optional.Some(new Uri(value)));
}
catch
{
return Task.FromResult(Optional<Uri>.None);
}
}
}
diff --git a/DisCatSharp.CommandsNext/Converters/TimeConverters.cs b/DisCatSharp.CommandsNext/Converters/TimeConverters.cs
index eeea636ef..173f97455 100644
--- a/DisCatSharp.CommandsNext/Converters/TimeConverters.cs
+++ b/DisCatSharp.CommandsNext/Converters/TimeConverters.cs
@@ -1,141 +1,141 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a date time converter.
/// </summary>
public class DateTimeConverter : IArgumentConverter<DateTime>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
Task<Optional<DateTime>> IArgumentConverter<DateTime>.ConvertAsync(string value, CommandContext ctx) =>
DateTime.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var result)
? Task.FromResult(Optional.Some(result))
: Task.FromResult(Optional<DateTime>.None);
}
/// <summary>
/// Represents a date time offset converter.
/// </summary>
public class DateTimeOffsetConverter : IArgumentConverter<DateTimeOffset>
{
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
Task<Optional<DateTimeOffset>> IArgumentConverter<DateTimeOffset>.ConvertAsync(string value, CommandContext ctx) =>
DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var result)
? Task.FromResult(Optional.Some(result))
: Task.FromResult(Optional<DateTimeOffset>.None);
}
/// <summary>
/// Represents a time span converter.
/// </summary>
public class TimeSpanConverter : IArgumentConverter<TimeSpan>
{
/// <summary>
/// Gets or sets the time span regex.
/// </summary>
private static Regex s_timeSpanRegex { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="TimeSpanConverter"/> class.
/// </summary>
static TimeSpanConverter()
{
s_timeSpanRegex = CommonRegEx.TimeSpan;
}
/// <summary>
/// Converts a string.
/// </summary>
/// <param name="value">The string to convert.</param>
/// <param name="ctx">The command context.</param>
Task<Optional<TimeSpan>> IArgumentConverter<TimeSpan>.ConvertAsync(string value, CommandContext ctx)
{
if (value == "0")
return Task.FromResult(Optional.Some(TimeSpan.Zero));
if (int.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out _))
return Task.FromResult(Optional<TimeSpan>.None);
if (!ctx.Config.CaseSensitive)
value = value.ToLowerInvariant();
if (TimeSpan.TryParse(value, CultureInfo.InvariantCulture, out var result))
return Task.FromResult(Optional.Some(result));
var gps = new string[] { "days", "hours", "minutes", "seconds" };
var mtc = s_timeSpanRegex.Match(value);
if (!mtc.Success)
return Task.FromResult(Optional<TimeSpan>.None);
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.Some(result));
}
}
diff --git a/DisCatSharp.CommandsNext/Entities/Builders/CommandBuilder.cs b/DisCatSharp.CommandsNext/Entities/Builders/CommandBuilder.cs
index e388bb253..081c7ca0d 100644
--- a/DisCatSharp.CommandsNext/Entities/Builders/CommandBuilder.cs
+++ b/DisCatSharp.CommandsNext/Entities/Builders/CommandBuilder.cs
@@ -1,305 +1,305 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.CommandsNext.Attributes;
using DisCatSharp.CommandsNext.Entities;
using DisCatSharp.CommandsNext.Exceptions;
namespace DisCatSharp.CommandsNext.Builders;
/// <summary>
/// Represents an interface to build a command.
/// </summary>
public class CommandBuilder
{
/// <summary>
/// Gets the name set for this command.
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Gets the aliases set for this command.
/// </summary>
public IReadOnlyList<string> Aliases { get; }
/// <summary>
/// Gets the alias list.
/// </summary>
private readonly List<string> _aliasList;
/// <summary>
/// Gets the description set for this command.
/// </summary>
public string Description { get; private set; }
/// <summary>
/// Gets whether this command will be hidden or not.
/// </summary>
public bool IsHidden { get; private set; }
/// <summary>
/// Gets the execution checks defined for this command.
/// </summary>
public IReadOnlyList<CheckBaseAttribute> ExecutionChecks { get; }
/// <summary>
/// Gets the execution check list.
/// </summary>
private readonly List<CheckBaseAttribute> _executionCheckList;
/// <summary>
/// Gets the collection of this command's overloads.
/// </summary>
public IReadOnlyList<CommandOverloadBuilder> Overloads { get; }
/// <summary>
/// Gets the overload list.
/// </summary>
private readonly List<CommandOverloadBuilder> _overloadList;
/// <summary>
/// Gets the overload argument sets.
/// </summary>
private readonly HashSet<string> _overloadArgumentSets;
/// <summary>
/// Gets the module on which this command is to be defined.
/// </summary>
public ICommandModule Module { get; }
/// <summary>
/// Gets custom attributes defined on this command.
/// </summary>
public IReadOnlyList<Attribute> CustomAttributes { get; }
/// <summary>
/// Gets the custom attribute list.
/// </summary>
private readonly List<Attribute> _customAttributeList;
/// <summary>
/// Creates a new module-less command builder.
/// </summary>
public CommandBuilder()
: this(null)
{ }
/// <summary>
/// Creates a new command builder.
/// </summary>
/// <param name="module">Module on which this command is to be defined.</param>
public CommandBuilder(ICommandModule module)
{
this._aliasList = new List<string>();
this.Aliases = new ReadOnlyCollection<string>(this._aliasList);
this._executionCheckList = new List<CheckBaseAttribute>();
this.ExecutionChecks = new ReadOnlyCollection<CheckBaseAttribute>(this._executionCheckList);
this._overloadArgumentSets = new HashSet<string>();
this._overloadList = new List<CommandOverloadBuilder>();
this.Overloads = new ReadOnlyCollection<CommandOverloadBuilder>(this._overloadList);
this.Module = module;
this._customAttributeList = new List<Attribute>();
this.CustomAttributes = new ReadOnlyCollection<Attribute>(this._customAttributeList);
}
/// <summary>
/// Sets the name for this command.
/// </summary>
/// <param name="name">Name for this command.</param>
/// <returns>This builder.</returns>
public CommandBuilder WithName(string name)
{
if (name == null || name.ToCharArray().Any(xc => char.IsWhiteSpace(xc)))
throw new ArgumentException("Command name cannot be null or contain any whitespace characters.", nameof(name));
if (this.Name != null)
throw new InvalidOperationException("This command already has a name.");
if (this._aliasList.Contains(name))
throw new ArgumentException("Command name cannot be one of its aliases.", nameof(name));
this.Name = name;
return this;
}
/// <summary>
/// Adds aliases to this command.
/// </summary>
/// <param name="aliases">Aliases to add to the command.</param>
/// <returns>This builder.</returns>
public CommandBuilder WithAliases(params string[] aliases)
{
if (aliases == null || !aliases.Any())
throw new ArgumentException("You need to pass at least one alias.", nameof(aliases));
foreach (var alias in aliases)
this.WithAlias(alias);
return this;
}
/// <summary>
/// Adds an alias to this command.
/// </summary>
/// <param name="alias">Alias to add to the command.</param>
/// <returns>This builder.</returns>
public CommandBuilder WithAlias(string alias)
{
if (alias.ToCharArray().Any(xc => char.IsWhiteSpace(xc)))
throw new ArgumentException("Aliases cannot contain whitespace characters or null strings.", nameof(alias));
if (this.Name == alias || this._aliasList.Contains(alias))
throw new ArgumentException("Aliases cannot contain the command name, and cannot be duplicate.", nameof(alias));
this._aliasList.Add(alias);
return this;
}
/// <summary>
/// Sets the description for this command.
/// </summary>
/// <param name="description">Description to use for this command.</param>
/// <returns>This builder.</returns>
public CommandBuilder WithDescription(string description)
{
this.Description = description;
return this;
}
/// <summary>
/// Sets whether this command is to be hidden.
/// </summary>
/// <param name="hidden">Whether the command is to be hidden.</param>
/// <returns>This builder.</returns>
public CommandBuilder WithHiddenStatus(bool hidden)
{
this.IsHidden = hidden;
return this;
}
/// <summary>
/// Adds pre-execution checks to this command.
/// </summary>
/// <param name="checks">Pre-execution checks to add to this command.</param>
/// <returns>This builder.</returns>
public CommandBuilder WithExecutionChecks(params CheckBaseAttribute[] checks)
{
this._executionCheckList.AddRange(checks.Except(this._executionCheckList));
return this;
}
/// <summary>
/// Adds a pre-execution check to this command.
/// </summary>
/// <param name="check">Pre-execution check to add to this command.</param>
/// <returns>This builder.</returns>
public CommandBuilder WithExecutionCheck(CheckBaseAttribute check)
{
if (!this._executionCheckList.Contains(check))
this._executionCheckList.Add(check);
return this;
}
/// <summary>
/// Adds overloads to this command. An executable command needs to have at least one overload.
/// </summary>
/// <param name="overloads">Overloads to add to this command.</param>
/// <returns>This builder.</returns>
public CommandBuilder WithOverloads(params CommandOverloadBuilder[] overloads)
{
foreach (var overload in overloads)
this.WithOverload(overload);
return this;
}
/// <summary>
/// Adds an overload to this command. An executable command needs to have at least one overload.
/// </summary>
/// <param name="overload">Overload to add to this command.</param>
/// <returns>This builder.</returns>
public CommandBuilder WithOverload(CommandOverloadBuilder overload)
{
if (this._overloadArgumentSets.Contains(overload.ArgumentSet))
throw new DuplicateOverloadException(this.Name, overload.Arguments.Select(x => x.Type).ToList(), overload.ArgumentSet);
this._overloadArgumentSets.Add(overload.ArgumentSet);
this._overloadList.Add(overload);
return this;
}
/// <summary>
/// Adds a custom attribute to this command. This can be used to indicate various custom information about a command.
/// </summary>
/// <param name="attribute">Attribute to add.</param>
/// <returns>This builder.</returns>
public CommandBuilder WithCustomAttribute(Attribute attribute)
{
this._customAttributeList.Add(attribute);
return this;
}
/// <summary>
/// Adds multiple custom attributes to this command. This can be used to indicate various custom information about a command.
/// </summary>
/// <param name="attributes">Attributes to add.</param>
/// <returns>This builder.</returns>
public CommandBuilder WithCustomAttributes(params Attribute[] attributes)
{
foreach (var attr in attributes)
this.WithCustomAttribute(attr);
return this;
}
/// <summary>
/// Builds the command.
/// </summary>
/// <param name="parent">The parent command group.</param>
internal virtual Command Build(CommandGroup parent)
{
var cmd = new Command
{
Name = this.Name,
Description = this.Description,
Aliases = this.Aliases,
ExecutionChecks = this.ExecutionChecks,
IsHidden = this.IsHidden,
Parent = parent,
Overloads = new ReadOnlyCollection<CommandOverload>(this.Overloads.Select(xo => xo.Build()).ToList()),
Module = this.Module,
CustomAttributes = this.CustomAttributes
};
return cmd;
}
}
diff --git a/DisCatSharp.CommandsNext/Entities/Builders/CommandGroupBuilder.cs b/DisCatSharp.CommandsNext/Entities/Builders/CommandGroupBuilder.cs
index 0892369d2..6db3341ca 100644
--- a/DisCatSharp.CommandsNext/Entities/Builders/CommandGroupBuilder.cs
+++ b/DisCatSharp.CommandsNext/Entities/Builders/CommandGroupBuilder.cs
@@ -1,101 +1,101 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Collections.ObjectModel;
using System.Linq;
using DisCatSharp.CommandsNext.Entities;
namespace DisCatSharp.CommandsNext.Builders;
/// <summary>
/// Represents an interface to build a command group.
/// </summary>
public sealed class CommandGroupBuilder : CommandBuilder
{
/// <summary>
/// Gets the list of child commands registered for this group.
/// </summary>
public IReadOnlyList<CommandBuilder> Children { get; }
/// <summary>
/// Gets the children list.
/// </summary>
private readonly List<CommandBuilder> _childrenList;
/// <summary>
/// Creates a new module-less command group builder.
/// </summary>
public CommandGroupBuilder()
: this(null)
{ }
/// <summary>
/// Creates a new command group builder.
/// </summary>
/// <param name="module">Module on which this group is to be defined.</param>
public CommandGroupBuilder(ICommandModule module)
: base(module)
{
this._childrenList = new List<CommandBuilder>();
this.Children = new ReadOnlyCollection<CommandBuilder>(this._childrenList);
}
/// <summary>
/// Adds a command to the collection of child commands for this group.
/// </summary>
/// <param name="child">Command to add to the collection of child commands for this group.</param>
/// <returns>This builder.</returns>
public CommandGroupBuilder WithChild(CommandBuilder child)
{
this._childrenList.Add(child);
return this;
}
/// <summary>
/// Builds the command group.
/// </summary>
/// <param name="parent">The parent command group.</param>
internal override Command Build(CommandGroup parent)
{
var cmd = new CommandGroup
{
Name = this.Name,
Description = this.Description,
Aliases = this.Aliases,
ExecutionChecks = this.ExecutionChecks,
IsHidden = this.IsHidden,
Parent = parent,
Overloads = new ReadOnlyCollection<CommandOverload>(this.Overloads.Select(xo => xo.Build()).ToList()),
Module = this.Module,
CustomAttributes = this.CustomAttributes
};
var cs = new List<Command>();
foreach (var xc in this.Children)
cs.Add(xc.Build(cmd));
cmd.Children = new ReadOnlyCollection<Command>(cs);
return cmd;
}
}
diff --git a/DisCatSharp.CommandsNext/Entities/Builders/CommandModuleBuilder.cs b/DisCatSharp.CommandsNext/Entities/Builders/CommandModuleBuilder.cs
index 47f5210da..5d4d10fbf 100644
--- a/DisCatSharp.CommandsNext/Entities/Builders/CommandModuleBuilder.cs
+++ b/DisCatSharp.CommandsNext/Entities/Builders/CommandModuleBuilder.cs
@@ -1,87 +1,87 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.CommandsNext.Attributes;
using DisCatSharp.CommandsNext.Entities;
namespace DisCatSharp.CommandsNext.Builders;
/// <summary>
/// Represents an interface to build a command module.
/// </summary>
public sealed class CommandModuleBuilder
{
/// <summary>
/// Gets the type this build will construct a module out of.
/// </summary>
public Type Type { get; private set; }
/// <summary>
/// Gets the lifespan for the built module.
/// </summary>
public ModuleLifespan Lifespan { get; private set; }
/// <summary>
/// Creates a new command module builder.
/// </summary>
public CommandModuleBuilder()
{ }
/// <summary>
/// Sets the type this builder will construct a module out of.
/// </summary>
/// <param name="t">Type to build a module out of. It has to derive from <see cref="BaseCommandModule"/>.</param>
/// <returns>This builder.</returns>
public CommandModuleBuilder WithType(Type t)
{
if (!t.IsModuleCandidateType())
throw new ArgumentException("Specified type is not a valid module type.", nameof(t));
this.Type = t;
return this;
}
/// <summary>
/// Lifespan to give this module.
/// </summary>
/// <param name="lifespan">Lifespan for this module.</param>
/// <returns>This builder.</returns>
public CommandModuleBuilder WithLifespan(ModuleLifespan lifespan)
{
this.Lifespan = lifespan;
return this;
}
/// <summary>
/// Builds the command module.
/// </summary>
/// <param name="services">The services.</param>
internal ICommandModule Build(IServiceProvider services) =>
this.Lifespan switch
{
ModuleLifespan.Singleton => new SingletonCommandModule(this.Type, services),
ModuleLifespan.Transient => new TransientCommandModule(this.Type),
_ => throw new NotSupportedException("Module lifespans other than transient and singleton are not supported."),
};
}
diff --git a/DisCatSharp.CommandsNext/Entities/Builders/CommandOverloadBuilder.cs b/DisCatSharp.CommandsNext/Entities/Builders/CommandOverloadBuilder.cs
index e9511d2fe..d129cd2d1 100644
--- a/DisCatSharp.CommandsNext/Entities/Builders/CommandOverloadBuilder.cs
+++ b/DisCatSharp.CommandsNext/Entities/Builders/CommandOverloadBuilder.cs
@@ -1,193 +1,193 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Linq.Expressions;
using System.Reflection;
using System.Text;
using DisCatSharp.CommandsNext.Attributes;
using DisCatSharp.CommandsNext.Exceptions;
namespace DisCatSharp.CommandsNext.Builders;
/// <summary>
/// Represents an interface to build a command overload.
/// </summary>
public sealed class CommandOverloadBuilder
{
/// <summary>
/// Gets a value that uniquely identifies an overload.
/// </summary>
internal string ArgumentSet { get; }
/// <summary>
/// Gets the collection of arguments this overload takes.
/// </summary>
public IReadOnlyList<CommandArgument> Arguments { get; }
/// <summary>
/// Gets this overload's priority when picking a suitable one for execution.
/// </summary>
public int Priority { get; set; }
/// <summary>
/// Gets the overload's callable delegate.
/// </summary>
public Delegate Callable { get; set; }
/// <summary>
/// Gets the invocation target.
/// </summary>
private readonly object _invocationTarget;
/// <summary>
/// Creates a new command overload builder from specified method.
/// </summary>
/// <param name="method">Method to use for this overload.</param>
public CommandOverloadBuilder(MethodInfo method)
: this(method, null)
{ }
/// <summary>
/// Creates a new command overload builder from specified delegate.
/// </summary>
/// <param name="method">Delegate to use for this overload.</param>
public CommandOverloadBuilder(Delegate method)
: this(method.GetMethodInfo(), method.Target)
{ }
/// <summary>
/// Prevents a default instance of the <see cref="CommandOverloadBuilder"/> class from being created.
/// </summary>
/// <param name="method">The method.</param>
/// <param name="target">The target.</param>
private CommandOverloadBuilder(MethodInfo method, object target)
{
if (!method.IsCommandCandidate(out var prms))
throw new ArgumentException("Specified method is not suitable for a command.", nameof(method));
this._invocationTarget = target;
// create the argument array
var ea = new ParameterExpression[prms.Length + 1];
var iep = Expression.Parameter(target?.GetType() ?? method.DeclaringType, "instance");
ea[0] = iep;
ea[1] = Expression.Parameter(typeof(CommandContext), "ctx");
var pri = method.GetCustomAttribute<PriorityAttribute>();
if (pri != null)
this.Priority = pri.Priority;
var i = 2;
var args = new List<CommandArgument>(prms.Length - 1);
var setb = new StringBuilder();
foreach (var arg in prms.Skip(1))
{
setb.Append(arg.ParameterType).Append(';');
var ca = new CommandArgument
{
Name = arg.Name,
Type = arg.ParameterType,
IsOptional = arg.IsOptional,
DefaultValue = arg.IsOptional ? arg.DefaultValue : null
};
var attrsCustom = new List<Attribute>();
var attrs = arg.GetCustomAttributes();
var isParams = false;
foreach (var xa in attrs)
{
switch (xa)
{
case DescriptionAttribute d:
ca.Description = d.Description;
break;
case RemainingTextAttribute r:
ca.IsCatchAll = true;
break;
case ParamArrayAttribute p:
ca.IsCatchAll = true;
ca.Type = arg.ParameterType.GetElementType();
ca.IsArray = true;
isParams = true;
break;
default:
attrsCustom.Add(xa);
break;
}
}
if (i > 2 && !ca.IsOptional && !ca.IsCatchAll && args[i - 3].IsOptional)
throw new InvalidOverloadException("Non-optional argument cannot appear after an optional one", method, arg);
if (arg.ParameterType.IsArray && !isParams)
throw new InvalidOverloadException("Cannot use array arguments without params modifier.", method, arg);
ca.CustomAttributes = new ReadOnlyCollection<Attribute>(attrsCustom);
args.Add(ca);
ea[i++] = Expression.Parameter(arg.ParameterType, arg.Name);
}
//var ec = Expression.Call(iev, method, ea.Skip(2));
var ec = Expression.Call(iep, method, ea.Skip(1));
var el = Expression.Lambda(ec, ea);
this.ArgumentSet = setb.ToString();
this.Arguments = new ReadOnlyCollection<CommandArgument>(args);
this.Callable = el.Compile();
}
/// <summary>
/// Sets the priority for this command overload.
/// </summary>
/// <param name="priority">Priority for this command overload.</param>
/// <returns>This builder.</returns>
public CommandOverloadBuilder WithPriority(int priority)
{
this.Priority = priority;
return this;
}
/// <summary>
/// Builds the command overload.
/// </summary>
internal CommandOverload Build()
{
var ovl = new CommandOverload()
{
Arguments = this.Arguments,
Priority = this.Priority,
Callable = this.Callable,
InvocationTarget = this._invocationTarget
};
return ovl;
}
}
diff --git a/DisCatSharp.CommandsNext/Entities/Command.cs b/DisCatSharp.CommandsNext/Entities/Command.cs
index d01e51d42..3b2352209 100644
--- a/DisCatSharp.CommandsNext/Entities/Command.cs
+++ b/DisCatSharp.CommandsNext/Entities/Command.cs
@@ -1,232 +1,232 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.CommandsNext.Attributes;
using DisCatSharp.CommandsNext.Entities;
namespace DisCatSharp.CommandsNext;
/// <summary>
/// Represents a command.
/// </summary>
public class Command
{
/// <summary>
/// Gets this command's name.
/// </summary>
public string Name { get; internal set; }
/// <summary>
/// Gets this command's qualified name (i.e. one that includes all module names).
/// </summary>
public string QualifiedName
=> this.Parent != null ? string.Concat(this.Parent.QualifiedName, " ", this.Name) : this.Name;
/// <summary>
/// Gets this command's aliases.
/// </summary>
public IReadOnlyList<string> Aliases { get; internal set; }
/// <summary>
/// Gets this command's parent module, if any.
/// </summary>
public CommandGroup Parent { get; internal set; }
/// <summary>
/// Gets this command's description.
/// </summary>
public string Description { get; internal set; }
/// <summary>
/// Gets whether this command is hidden.
/// </summary>
public bool IsHidden { get; internal set; }
/// <summary>
/// Gets a collection of pre-execution checks for this command.
/// </summary>
public IReadOnlyList<CheckBaseAttribute> ExecutionChecks { get; internal set; }
/// <summary>
/// Gets a collection of this command's overloads.
/// </summary>
public IReadOnlyList<CommandOverload> Overloads { get; internal set; }
/// <summary>
/// Gets the module in which this command is defined.
/// </summary>
public ICommandModule Module { get; internal set; }
/// <summary>
/// Gets the custom attributes defined on this command.
/// </summary>
public IReadOnlyList<Attribute> CustomAttributes { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="Command"/> class.
/// </summary>
internal Command() { }
/// <summary>
/// Executes this command with specified context.
/// </summary>
/// <param name="ctx">Context to execute the command in.</param>
/// <returns>Command's execution results.</returns>
public virtual async Task<CommandResult> ExecuteAsync(CommandContext ctx)
{
CommandResult res = default;
try
{
var executed = false;
foreach (var ovl in this.Overloads.OrderByDescending(x => x.Priority))
{
ctx.Overload = ovl;
var args = await CommandsNextUtilities.BindArguments(ctx, ctx.Config.IgnoreExtraArguments).ConfigureAwait(false);
if (!args.IsSuccessful)
continue;
ctx.RawArguments = args.Raw;
var mdl = ovl.InvocationTarget ?? this.Module?.GetInstance(ctx.Services);
if (mdl is BaseCommandModule bcmBefore)
await bcmBefore.BeforeExecutionAsync(ctx).ConfigureAwait(false);
args.Converted[0] = mdl;
var ret = (Task)ovl.Callable.DynamicInvoke(args.Converted);
await ret.ConfigureAwait(false);
executed = true;
res = new CommandResult
{
IsSuccessful = true,
Context = ctx
};
if (mdl is BaseCommandModule bcmAfter)
await bcmAfter.AfterExecutionAsync(ctx).ConfigureAwait(false);
break;
}
if (!executed)
throw new ArgumentException("Could not find a suitable overload for the command.");
}
catch (Exception ex)
{
res = new CommandResult
{
IsSuccessful = false,
Exception = ex,
Context = ctx
};
}
return res;
}
/// <summary>
/// Runs pre-execution checks for this command and returns any that fail for given context.
/// </summary>
/// <param name="ctx">Context in which the command is executed.</param>
/// <param name="help">Whether this check is being executed from help or not. This can be used to probe whether command can be run without setting off certain fail conditions (such as cooldowns).</param>
/// <returns>Pre-execution checks that fail for given context.</returns>
public async Task<IEnumerable<CheckBaseAttribute>> RunChecksAsync(CommandContext ctx, bool help)
{
var fchecks = new List<CheckBaseAttribute>();
if (this.ExecutionChecks != null && this.ExecutionChecks.Any())
foreach (var ec in this.ExecutionChecks)
if (!await ec.ExecuteCheckAsync(ctx, help).ConfigureAwait(false))
fchecks.Add(ec);
return fchecks;
}
/// <summary>
/// Checks whether this command is equal to another one.
/// </summary>
/// <param name="cmd1">Command to compare to.</param>
/// <param name="cmd2">Command to compare.</param>
/// <returns>Whether the two commands are equal.</returns>
public static bool operator ==(Command cmd1, Command cmd2)
{
var o1 = cmd1 as object;
var o2 = cmd2 as object;
if (o1 == null && o2 != null)
return false;
else if (o1 != null && o2 == null)
return false;
else if (o1 == null && o2 == null)
return true;
return cmd1.QualifiedName == cmd2.QualifiedName;
}
/// <summary>
/// Checks whether this command is not equal to another one.
/// </summary>
/// <param name="cmd1">Command to compare to.</param>
/// <param name="cmd2">Command to compare.</param>
/// <returns>Whether the two commands are not equal.</returns>
public static bool operator !=(Command cmd1, Command cmd2)
=> !(cmd1 == cmd2);
/// <summary>
/// Checks whether this command equals another object.
/// </summary>
/// <param name="obj">Object to compare to.</param>
/// <returns>Whether this command is equal to another object.</returns>
public override bool Equals(object obj)
{
var o1 = obj;
var o2 = this as object;
if (o1 == null && o2 != null)
return false;
else if (o1 != null && o2 == null)
return false;
else if (o1 == null && o2 == null)
return true;
return obj is Command cmd
&& cmd.QualifiedName == this.QualifiedName;
}
/// <summary>
/// Gets this command's hash code.
/// </summary>
/// <returns>This command's hash code.</returns>
public override int GetHashCode() => this.QualifiedName.GetHashCode();
/// <summary>
/// Returns a string representation of this command.
/// </summary>
/// <returns>String representation of this command.</returns>
public override string ToString() =>
this is CommandGroup g
? $"Command Group: {this.QualifiedName}, {g.Children.Count} top-level children"
: $"Command: {this.QualifiedName}";
}
diff --git a/DisCatSharp.CommandsNext/Entities/CommandArgument.cs b/DisCatSharp.CommandsNext/Entities/CommandArgument.cs
index 267d09b38..60539962c 100644
--- a/DisCatSharp.CommandsNext/Entities/CommandArgument.cs
+++ b/DisCatSharp.CommandsNext/Entities/CommandArgument.cs
@@ -1,72 +1,72 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.CommandsNext;
/// <summary>
/// Represents a command argument.
/// </summary>
public sealed class CommandArgument
{
/// <summary>
/// Gets this argument's name.
/// </summary>
public string Name { get; internal set; }
/// <summary>
/// Gets this argument's type.
/// </summary>
public Type Type { get; internal set; }
/// <summary>
/// Gets or sets whether this argument is an array argument.
/// </summary>
internal bool IsArray { get; set; } = false;
/// <summary>
/// Gets whether this argument is optional.
/// </summary>
public bool IsOptional { get; internal set; }
/// <summary>
/// Gets this argument's default value.
/// </summary>
public object DefaultValue { get; internal set; }
/// <summary>
/// Gets whether this argument catches all remaining arguments.
/// </summary>
public bool IsCatchAll { get; internal set; }
/// <summary>
/// Gets this argument's description.
/// </summary>
public string Description { get; internal set; }
/// <summary>
/// Gets the custom attributes attached to this argument.
/// </summary>
public IReadOnlyCollection<Attribute> CustomAttributes { get; internal set; }
}
diff --git a/DisCatSharp.CommandsNext/Entities/CommandGroup.cs b/DisCatSharp.CommandsNext/Entities/CommandGroup.cs
index a52164231..765741eba 100644
--- a/DisCatSharp.CommandsNext/Entities/CommandGroup.cs
+++ b/DisCatSharp.CommandsNext/Entities/CommandGroup.cs
@@ -1,103 +1,103 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.CommandsNext.Exceptions;
namespace DisCatSharp.CommandsNext;
/// <summary>
/// Represents a command group.
/// </summary>
public class CommandGroup : Command
{
/// <summary>
/// Gets all the commands that belong to this module.
/// </summary>
public IReadOnlyList<Command> Children { get; internal set; }
/// <summary>
/// Gets whether this command is executable without subcommands.
/// </summary>
public bool IsExecutableWithoutSubcommands => this.Overloads?.Any() == true;
/// <summary>
/// Initializes a new instance of the <see cref="CommandGroup"/> class.
/// </summary>
internal CommandGroup() : base() { }
/// <summary>
/// Executes this command or its subcommand with specified context.
/// </summary>
/// <param name="ctx">Context to execute the command in.</param>
/// <returns>Command's execution results.</returns>
public override async Task<CommandResult> ExecuteAsync(CommandContext ctx)
{
var findPos = 0;
var cn = CommandsNextUtilities.ExtractNextArgument(ctx.RawArgumentString, ref findPos);
if (cn != null)
{
var cmd = ctx.Config.CaseSensitive
? this.Children.FirstOrDefault(xc => xc.Name == cn || (xc.Aliases != null && xc.Aliases.Contains(cn)))
: this.Children.FirstOrDefault(xc => xc.Name.ToLowerInvariant() == cn.ToLowerInvariant() || (xc.Aliases != null && xc.Aliases.Select(xs => xs.ToLowerInvariant()).Contains(cn.ToLowerInvariant())));
if (cmd != null)
{
// pass the execution on
var xctx = new CommandContext
{
Client = ctx.Client,
Message = ctx.Message,
Command = cmd,
Config = ctx.Config,
RawArgumentString = ctx.RawArgumentString[findPos..],
Prefix = ctx.Prefix,
CommandsNext = ctx.CommandsNext,
Services = ctx.Services
};
var fchecks = await cmd.RunChecksAsync(xctx, false).ConfigureAwait(false);
return fchecks.Any()
? new CommandResult
{
IsSuccessful = false,
Exception = new ChecksFailedException(cmd, xctx, fchecks),
Context = xctx
}
: await cmd.ExecuteAsync(xctx).ConfigureAwait(false);
}
}
return !this.IsExecutableWithoutSubcommands
? new CommandResult
{
IsSuccessful = false,
Exception = new InvalidOperationException("No matching subcommands were found, and this group is not executable."),
Context = ctx
}
: await base.ExecuteAsync(ctx).ConfigureAwait(false);
}
}
diff --git a/DisCatSharp.CommandsNext/Entities/CommandHelpMessage.cs b/DisCatSharp.CommandsNext/Entities/CommandHelpMessage.cs
index 597ccfaf2..f0057ed27 100644
--- a/DisCatSharp.CommandsNext/Entities/CommandHelpMessage.cs
+++ b/DisCatSharp.CommandsNext/Entities/CommandHelpMessage.cs
@@ -1,52 +1,52 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
namespace DisCatSharp.CommandsNext.Entities;
/// <summary>
/// Represents a formatted help message.
/// </summary>
public readonly struct CommandHelpMessage
{
/// <summary>
/// Gets the contents of the help message.
/// </summary>
public string Content { get; }
/// <summary>
/// Gets the embed attached to the help message.
/// </summary>
public DiscordEmbed Embed { get; }
/// <summary>
/// Creates a new instance of a help message.
/// </summary>
/// <param name="content">Contents of the message.</param>
/// <param name="embed">Embed to attach to the message.</param>
public CommandHelpMessage(string content = null, DiscordEmbed embed = null)
{
this.Content = content;
this.Embed = embed;
}
}
diff --git a/DisCatSharp.CommandsNext/Entities/CommandModule.cs b/DisCatSharp.CommandsNext/Entities/CommandModule.cs
index 11a35cd5f..525f9a832 100644
--- a/DisCatSharp.CommandsNext/Entities/CommandModule.cs
+++ b/DisCatSharp.CommandsNext/Entities/CommandModule.cs
@@ -1,106 +1,106 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.CommandsNext.Entities;
/// <summary>
/// Represents a base interface for all types of command modules.
/// </summary>
public interface ICommandModule
{
/// <summary>
/// Gets the type of this module.
/// </summary>
Type ModuleType { get; }
/// <summary>
/// Returns an instance of this module.
/// </summary>
/// <param name="services">Services to instantiate the module with.</param>
/// <returns>A created instance of this module.</returns>
BaseCommandModule GetInstance(IServiceProvider services);
}
/// <summary>
/// Represents a transient command module. This type of module is reinstantiated on every command call.
/// </summary>
public class TransientCommandModule : ICommandModule
{
/// <summary>
/// Gets the type of this module.
/// </summary>
public Type ModuleType { get; }
/// <summary>
/// Creates a new transient module.
/// </summary>
/// <param name="t">Type of the module to create.</param>
internal TransientCommandModule(Type t)
{
this.ModuleType = t;
}
/// <summary>
/// Creates a new instance of this module.
/// </summary>
/// <param name="services">Services to instantiate the module with.</param>
/// <returns>Created module.</returns>
public BaseCommandModule GetInstance(IServiceProvider services)
=> this.ModuleType.CreateInstance(services) as BaseCommandModule;
}
/// <summary>
/// Represents a singleton command module. This type of module is instantiated only when created.
/// </summary>
public class SingletonCommandModule : ICommandModule
{
/// <summary>
/// Gets the type of this module.
/// </summary>
public Type ModuleType { get; }
/// <summary>
/// Gets this module's instance.
/// </summary>
public BaseCommandModule Instance { get; }
/// <summary>
/// Creates a new singleton module, and instantiates it.
/// </summary>
/// <param name="t">Type of the module to create.</param>
/// <param name="services">Services to instantiate the module with.</param>
internal SingletonCommandModule(Type t, IServiceProvider services)
{
this.ModuleType = t;
this.Instance = t.CreateInstance(services) as BaseCommandModule;
}
/// <summary>
/// Returns the instance of this module.
/// </summary>
/// <param name="services">Services to instantiate the module with.</param>
/// <returns>This module's instance.</returns>
public BaseCommandModule GetInstance(IServiceProvider services)
=> this.Instance;
}
diff --git a/DisCatSharp.CommandsNext/Entities/CommandOverload.cs b/DisCatSharp.CommandsNext/Entities/CommandOverload.cs
index 2467c7e51..59f4d70b6 100644
--- a/DisCatSharp.CommandsNext/Entities/CommandOverload.cs
+++ b/DisCatSharp.CommandsNext/Entities/CommandOverload.cs
@@ -1,57 +1,57 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.CommandsNext;
/// <summary>
/// Represents a specific overload of a command.
/// </summary>
public sealed class CommandOverload
{
/// <summary>
/// Gets this command overload's arguments.
/// </summary>
public IReadOnlyList<CommandArgument> Arguments { get; internal set; }
/// <summary>
/// Gets this command overload's priority.
/// </summary>
public int Priority { get; internal set; }
/// <summary>
/// Gets this command overload's delegate.
/// </summary>
internal Delegate Callable { get; set; }
/// <summary>
/// Gets or sets the invocation target.
/// </summary>
internal object InvocationTarget { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="CommandOverload"/> class.
/// </summary>
internal CommandOverload() { }
}
diff --git a/DisCatSharp.CommandsNext/Entities/CommandResult.cs b/DisCatSharp.CommandsNext/Entities/CommandResult.cs
index 6d6750150..2b33aa04c 100644
--- a/DisCatSharp.CommandsNext/Entities/CommandResult.cs
+++ b/DisCatSharp.CommandsNext/Entities/CommandResult.cs
@@ -1,46 +1,46 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.CommandsNext;
/// <summary>
/// Represents a command's execution result.
/// </summary>
public struct CommandResult
{
/// <summary>
/// Gets whether the command execution succeeded.
/// </summary>
public bool IsSuccessful { get; internal set; }
/// <summary>
/// Gets the exception (if any) that occurred when executing the command.
/// </summary>
public Exception Exception { get; internal set; }
/// <summary>
/// Gets the context in which the command was executed.
/// </summary>
public CommandContext Context { get; internal set; }
}
diff --git a/DisCatSharp.CommandsNext/EventArgs/CommandContext.cs b/DisCatSharp.CommandsNext/EventArgs/CommandContext.cs
index 63ade7030..cf64c5344 100644
--- a/DisCatSharp.CommandsNext/EventArgs/CommandContext.cs
+++ b/DisCatSharp.CommandsNext/EventArgs/CommandContext.cs
@@ -1,207 +1,207 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 Microsoft.Extensions.DependencyInjection;
namespace DisCatSharp.CommandsNext;
/// <summary>
/// Represents a context in which a command is executed.
/// </summary>
public sealed class CommandContext
{
/// <summary>
/// Gets the client which received the message.
/// </summary>
public DiscordClient Client { get; internal set; }
/// <summary>
/// Gets the message that triggered the execution.
/// </summary>
public DiscordMessage Message { get; internal set; }
/// <summary>
/// Gets the channel in which the execution was triggered,
/// </summary>
public DiscordChannel Channel
=> this.Message.Channel;
/// <summary>
/// Gets the guild in which the execution was triggered. This property is null for commands sent over direct messages.
/// </summary>
public DiscordGuild Guild
=> this.Message.GuildId.HasValue ? this.Message.Guild : null;
/// <summary>
/// Gets the user who triggered the execution.
/// </summary>
public DiscordUser User
=> this.Message.Author;
/// <summary>
/// Gets the member who triggered the execution. This property is null for commands sent over direct messages.
/// </summary>
public DiscordMember Member
=> this._lazyMember.Value;
private readonly Lazy<DiscordMember> _lazyMember;
/// <summary>
/// Gets the CommandsNext service instance that handled this command.
/// </summary>
public CommandsNextExtension CommandsNext { get; internal set; }
/// <summary>
/// Gets the service provider for this CNext instance.
/// </summary>
public IServiceProvider Services { get; internal set; }
/// <summary>
/// Gets the command that is being executed.
/// </summary>
public Command Command { get; internal set; }
/// <summary>
/// Gets the overload of the command that is being executed.
/// </summary>
public CommandOverload Overload { get; internal set; }
/// <summary>
/// Gets the list of raw arguments passed to the command.
/// </summary>
public IReadOnlyList<string> RawArguments { get; internal set; }
/// <summary>
/// Gets the raw string from which the arguments were extracted.
/// </summary>
public string RawArgumentString { get; internal set; }
/// <summary>
/// Gets the prefix used to invoke the command.
/// </summary>
public string Prefix { get; internal set; }
/// <summary>
/// Gets or sets the config.
/// </summary>
internal CommandsNextConfiguration Config { get; set; }
/// <summary>
/// Gets or sets the service scope context.
/// </summary>
internal ServiceContext ServiceScopeContext { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="CommandContext"/> class.
/// </summary>
internal CommandContext()
{
this._lazyMember = new Lazy<DiscordMember>(() => this.Guild != null && this.Guild.Members.TryGetValue(this.User.Id, out var member) ? member : this.Guild?.GetMemberAsync(this.User.Id).ConfigureAwait(false).GetAwaiter().GetResult());
}
/// <summary>
/// Quickly respond to the message that triggered the command.
/// </summary>
/// <param name="content">Message to respond with.</param>
/// <returns></returns>
public Task<DiscordMessage> RespondAsync(string content)
=> this.Message.RespondAsync(content);
/// <summary>
/// Quickly respond to the message that triggered the command.
/// </summary>
/// <param name="embed">Embed to attach.</param>
/// <returns></returns>
public Task<DiscordMessage> RespondAsync(DiscordEmbed embed)
=> this.Message.RespondAsync(embed);
/// <summary>
/// Quickly respond to the message that triggered the command.
/// </summary>
/// <param name="content">Message to respond with.</param>
/// <param name="embed">Embed to attach.</param>
/// <returns></returns>
public Task<DiscordMessage> RespondAsync(string content, DiscordEmbed embed)
=> this.Message.RespondAsync(content, embed);
/// <summary>
/// Quickly respond to the message that triggered the command.
/// </summary>
/// <param name="builder">The Discord Message builder.</param>
/// <returns></returns>
public Task<DiscordMessage> RespondAsync(DiscordMessageBuilder builder)
=> this.Message.RespondAsync(builder);
/// <summary>
/// Quickly respond to the message that triggered the command.
/// </summary>
/// <param name="action">The Discord Message builder.</param>
/// <returns></returns>
public Task<DiscordMessage> RespondAsync(Action<DiscordMessageBuilder> action)
=> this.Message.RespondAsync(action);
/// <summary>
/// Triggers typing in the channel containing the message that triggered the command.
/// </summary>
/// <returns></returns>
public Task TriggerTypingAsync()
=> this.Channel.TriggerTypingAsync();
internal readonly struct ServiceContext : IDisposable
{
/// <summary>
/// Gets the provider.
/// </summary>
public IServiceProvider Provider { get; }
/// <summary>
/// Gets the scope.
/// </summary>
public IServiceScope Scope { get; }
/// <summary>
/// Gets a value indicating whether is initialized.
/// </summary>
public bool IsInitialized { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ServiceContext"/> class.
/// </summary>
/// <param name="services">The services.</param>
/// <param name="scope">The scope.</param>
public ServiceContext(IServiceProvider services, IServiceScope scope)
{
this.Provider = services;
this.Scope = scope;
this.IsInitialized = true;
}
/// <summary>
/// Disposes the command context.
/// </summary>
public void Dispose() => this.Scope?.Dispose();
}
}
diff --git a/DisCatSharp.CommandsNext/EventArgs/CommandErrorEventArgs.cs b/DisCatSharp.CommandsNext/EventArgs/CommandErrorEventArgs.cs
index c1ffaad85..19630d02a 100644
--- a/DisCatSharp.CommandsNext/EventArgs/CommandErrorEventArgs.cs
+++ b/DisCatSharp.CommandsNext/EventArgs/CommandErrorEventArgs.cs
@@ -1,43 +1,43 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.CommandsNext;
/// <summary>
/// Represents arguments for <see cref="CommandsNextExtension.CommandErrored"/> event.
/// </summary>
public class CommandErrorEventArgs : CommandEventArgs
{
/// <summary>
/// Gets the exception.
/// </summary>
public Exception Exception { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="CommandErrorEventArgs"/> class.
/// </summary>
/// <param name="provider">The provider.</param>
public CommandErrorEventArgs(IServiceProvider provider) : base(provider)
{ }
}
diff --git a/DisCatSharp.CommandsNext/EventArgs/CommandEventArgs.cs b/DisCatSharp.CommandsNext/EventArgs/CommandEventArgs.cs
index fb422fabc..9ab495832 100644
--- a/DisCatSharp.CommandsNext/EventArgs/CommandEventArgs.cs
+++ b/DisCatSharp.CommandsNext/EventArgs/CommandEventArgs.cs
@@ -1,51 +1,51 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.EventArgs;
namespace DisCatSharp.CommandsNext;
/// <summary>
/// Base class for all CNext-related events.
/// </summary>
public class CommandEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the context in which the command was executed.
/// </summary>
public CommandContext Context { get; internal set; }
/// <summary>
/// Gets the command that was executed.
/// </summary>
public Command Command
=> this.Context.Command;
/// <summary>
/// Initializes a new instance of the <see cref="CommandEventArgs"/> class.
/// </summary>
/// <param name="provider">The provider.</param>
public CommandEventArgs(IServiceProvider provider) : base(provider)
{ }
}
diff --git a/DisCatSharp.CommandsNext/EventArgs/CommandExecutionEventArgs.cs b/DisCatSharp.CommandsNext/EventArgs/CommandExecutionEventArgs.cs
index d5125a2a9..6b956ce22 100644
--- a/DisCatSharp.CommandsNext/EventArgs/CommandExecutionEventArgs.cs
+++ b/DisCatSharp.CommandsNext/EventArgs/CommandExecutionEventArgs.cs
@@ -1,38 +1,38 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.CommandsNext;
/// <summary>
/// Represents arguments for <see cref="CommandsNextExtension.CommandExecuted"/> event.
/// </summary>
public class CommandExecutionEventArgs : CommandEventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="CommandExecutionEventArgs"/> class.
/// </summary>
/// <param name="provider">The provider.</param>
public CommandExecutionEventArgs(IServiceProvider provider) : base(provider)
{ }
}
diff --git a/DisCatSharp.CommandsNext/Exceptions/ChecksFailedException.cs b/DisCatSharp.CommandsNext/Exceptions/ChecksFailedException.cs
index 49518eeaf..b2763f8b9 100644
--- a/DisCatSharp.CommandsNext/Exceptions/ChecksFailedException.cs
+++ b/DisCatSharp.CommandsNext/Exceptions/ChecksFailedException.cs
@@ -1,64 +1,64 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.CommandsNext.Attributes;
namespace DisCatSharp.CommandsNext.Exceptions;
/// <summary>
/// Indicates that one or more checks for given command have failed.
/// </summary>
public class ChecksFailedException : Exception
{
/// <summary>
/// Gets the command that was executed.
/// </summary>
public Command Command { get; }
/// <summary>
/// Gets the context in which given command was executed.
/// </summary>
public CommandContext Context { get; }
/// <summary>
/// Gets the checks that failed.
/// </summary>
public IReadOnlyList<CheckBaseAttribute> FailedChecks { get; }
/// <summary>
/// Creates a new <see cref="ChecksFailedException"/>.
/// </summary>
/// <param name="command">Command that failed to execute.</param>
/// <param name="ctx">Context in which the command was executed.</param>
/// <param name="failedChecks">A collection of checks that failed.</param>
public ChecksFailedException(Command command, CommandContext ctx, IEnumerable<CheckBaseAttribute> failedChecks)
: base("One or more pre-execution checks failed.")
{
this.Command = command;
this.Context = ctx;
this.FailedChecks = new ReadOnlyCollection<CheckBaseAttribute>(new List<CheckBaseAttribute>(failedChecks));
}
}
diff --git a/DisCatSharp.CommandsNext/Exceptions/CommandNotFoundException.cs b/DisCatSharp.CommandsNext/Exceptions/CommandNotFoundException.cs
index db0bebb90..fbe85bccd 100644
--- a/DisCatSharp.CommandsNext/Exceptions/CommandNotFoundException.cs
+++ b/DisCatSharp.CommandsNext/Exceptions/CommandNotFoundException.cs
@@ -1,52 +1,52 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.CommandsNext.Exceptions;
/// <summary>
/// Thrown when the command service fails to find a command.
/// </summary>
public sealed class CommandNotFoundException : Exception
{
/// <summary>
/// Gets the name of the command that was not found.
/// </summary>
public string CommandName { get; set; }
/// <summary>
/// Creates a new <see cref="CommandNotFoundException"/>.
/// </summary>
/// <param name="command">Name of the command that was not found.</param>
public CommandNotFoundException(string command)
: base("Specified command was not found.")
{
this.CommandName = command;
}
/// <summary>
/// Returns a string representation of this <see cref="CommandNotFoundException"/>.
/// </summary>
/// <returns>A string representation.</returns>
public override string ToString() => $"{this.GetType()}: {this.Message}\nCommand name: {this.CommandName}"; // much like System.ArgumentNullException works
}
diff --git a/DisCatSharp.CommandsNext/Exceptions/DuplicateCommandException.cs b/DisCatSharp.CommandsNext/Exceptions/DuplicateCommandException.cs
index 597b2176e..b5e82c3a7 100644
--- a/DisCatSharp.CommandsNext/Exceptions/DuplicateCommandException.cs
+++ b/DisCatSharp.CommandsNext/Exceptions/DuplicateCommandException.cs
@@ -1,52 +1,52 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.CommandsNext.Exceptions;
/// <summary>
/// Indicates that given command name or alias is taken.
/// </summary>
public class DuplicateCommandException : Exception
{
/// <summary>
/// Gets the name of the command that already exists.
/// </summary>
public string CommandName { get; }
/// <summary>
/// Creates a new exception indicating that given command name is already taken.
/// </summary>
/// <param name="name">Name of the command that was taken.</param>
internal DuplicateCommandException(string name)
: base("A command with specified name already exists.")
{
this.CommandName = name;
}
/// <summary>
/// Returns a string representation of this <see cref="DuplicateCommandException"/>.
/// </summary>
/// <returns>A string representation.</returns>
public override string ToString() => $"{this.GetType()}: {this.Message}\nCommand name: {this.CommandName}"; // much like System.ArgumentException works
}
diff --git a/DisCatSharp.CommandsNext/Exceptions/DuplicateOverloadException.cs b/DisCatSharp.CommandsNext/Exceptions/DuplicateOverloadException.cs
index f1ad32f48..34a27478e 100644
--- a/DisCatSharp.CommandsNext/Exceptions/DuplicateOverloadException.cs
+++ b/DisCatSharp.CommandsNext/Exceptions/DuplicateOverloadException.cs
@@ -1,68 +1,68 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
namespace DisCatSharp.CommandsNext.Exceptions;
/// <summary>
/// Indicates that given argument set already exists as an overload for specified command.
/// </summary>
public class DuplicateOverloadException : Exception
{
/// <summary>
/// Gets the name of the command that already has the overload.
/// </summary>
public string CommandName { get; }
/// <summary>
/// Gets the ordered collection of argument types for the specified overload.
/// </summary>
public IReadOnlyList<Type> ArgumentTypes { get; }
/// <summary>
/// Gets the argument set key.
/// </summary>
private readonly string _argumentSetKey;
/// <summary>
/// Creates a new exception indicating given argument set already exists as an overload for specified command.
/// </summary>
/// <param name="name">Name of the command with duplicated argument sets.</param>
/// <param name="argumentTypes">Collection of ordered argument types for the command.</param>
/// <param name="argumentSetKey">Overload identifier.</param>
internal DuplicateOverloadException(string name, IList<Type> argumentTypes, string argumentSetKey)
: base("An overload with specified argument types exists.")
{
this.CommandName = name;
this.ArgumentTypes = new ReadOnlyCollection<Type>(argumentTypes);
this._argumentSetKey = argumentSetKey;
}
/// <summary>
/// Returns a string representation of this <see cref="DuplicateOverloadException"/>.
/// </summary>
/// <returns>A string representation.</returns>
public override string ToString() => $"{this.GetType()}: {this.Message}\nCommand name: {this.CommandName}\nArgument types: {this._argumentSetKey}"; // much like System.ArgumentException works
}
diff --git a/DisCatSharp.CommandsNext/Exceptions/InvalidOverloadException.cs b/DisCatSharp.CommandsNext/Exceptions/InvalidOverloadException.cs
index 7626de47b..1a7afa1ba 100644
--- a/DisCatSharp.CommandsNext/Exceptions/InvalidOverloadException.cs
+++ b/DisCatSharp.CommandsNext/Exceptions/InvalidOverloadException.cs
@@ -1,74 +1,74 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Reflection;
namespace DisCatSharp.CommandsNext.Exceptions;
/// <summary>
/// Thrown when the command service fails to build a command due to a problem with its overload.
/// </summary>
public sealed class InvalidOverloadException : Exception
{
/// <summary>
/// Gets the method that caused this exception.
/// </summary>
public MethodInfo Method { get; }
/// <summary>
/// Gets or sets the argument that caused the problem. This can be null.
/// </summary>
public ParameterInfo Parameter { get; }
/// <summary>
/// Creates a new <see cref="InvalidOverloadException"/>.
/// </summary>
/// <param name="message">Exception message.</param>
/// <param name="method">Method that caused the problem.</param>
/// <param name="parameter">Method argument that caused the problem.</param>
public InvalidOverloadException(string message, MethodInfo method, ParameterInfo parameter)
: base(message)
{
this.Method = method;
this.Parameter = parameter;
}
/// <summary>
/// Creates a new <see cref="InvalidOverloadException"/>.
/// </summary>
/// <param name="message">Exception message.</param>
/// <param name="method">Method that caused the problem.</param>
public InvalidOverloadException(string message, MethodInfo method)
: this(message, method, null)
{ }
/// <summary>
/// Returns a string representation of this <see cref="InvalidOverloadException"/>.
/// </summary>
/// <returns>A string representation.</returns>
public override string ToString() =>
// much like System.ArgumentNullException works
this.Parameter == null
? $"{this.GetType()}: {this.Message}\nMethod: {this.Method} (declared in {this.Method.DeclaringType})"
: $"{this.GetType()}: {this.Message}\nMethod: {this.Method} (declared in {this.Method.DeclaringType})\nArgument: {this.Parameter.ParameterType} {this.Parameter.Name}";
}
diff --git a/DisCatSharp.CommandsNext/ExtensionMethods.cs b/DisCatSharp.CommandsNext/ExtensionMethods.cs
index e9c6eb666..0823f135f 100644
--- a/DisCatSharp.CommandsNext/ExtensionMethods.cs
+++ b/DisCatSharp.CommandsNext/ExtensionMethods.cs
@@ -1,213 +1,213 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Builders;
using DisCatSharp.CommandsNext.Converters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace DisCatSharp.CommandsNext;
/// <summary>
/// Defines various extensions specific to CommandsNext.
/// </summary>
public static class ExtensionMethods
{
/// <summary>
/// Enables CommandsNext module on this <see cref="DiscordClient"/>.
/// </summary>
/// <param name="client">Client to enable CommandsNext for.</param>
/// <param name="cfg">CommandsNext configuration to use.</param>
/// <returns>Created <see cref="CommandsNextExtension"/>.</returns>
public static CommandsNextExtension UseCommandsNext(this DiscordClient client, CommandsNextConfiguration cfg)
{
if (client.GetExtension<CommandsNextExtension>() != null)
throw new InvalidOperationException("CommandsNext is already enabled for that client.");
if (!Utilities.HasMessageIntents(client.Configuration.Intents))
client.Logger.LogCritical(CommandsNextEvents.Intents, "The CommandsNext extension is registered but there are no message intents enabled. It is highly recommended to enable them.");
if (!client.Configuration.Intents.HasIntent(DiscordIntents.Guilds))
client.Logger.LogCritical(CommandsNextEvents.Intents, "The CommandsNext extension is registered but the guilds intent is not enabled. It is highly recommended to enable it.");
cfg.ServiceProvider ??= client.ServiceProvider ?? new ServiceCollection().BuildServiceProvider(true);
var cnext = new CommandsNextExtension(cfg);
client.AddExtension(cnext);
return cnext;
}
/// <summary>
/// Enables CommandsNext module on all shards in this <see cref="DiscordShardedClient"/>.
/// </summary>
/// <param name="client">Client to enable CommandsNext for.</param>
/// <param name="cfg">CommandsNext configuration to use.</param>
/// <returns>A dictionary of created <see cref="CommandsNextExtension"/>, indexed by shard id.</returns>
public static async Task<IReadOnlyDictionary<int, CommandsNextExtension>> UseCommandsNextAsync(this DiscordShardedClient client, CommandsNextConfiguration cfg)
{
var modules = new Dictionary<int, CommandsNextExtension>();
await client.InitializeShardsAsync().ConfigureAwait(false);
foreach (var shard in client.ShardClients.Select(xkvp => xkvp.Value))
{
var cnext = shard.GetExtension<CommandsNextExtension>();
cnext ??= shard.UseCommandsNext(cfg);
modules[shard.ShardId] = cnext;
}
return new ReadOnlyDictionary<int, CommandsNextExtension>(modules);
}
/// <summary>
/// Gets the active CommandsNext module for this client.
/// </summary>
/// <param name="client">Client to get CommandsNext module from.</param>
/// <returns>The module, or null if not activated.</returns>
public static CommandsNextExtension GetCommandsNext(this DiscordClient client)
=> client.GetExtension<CommandsNextExtension>();
/// <summary>
/// Gets the active CommandsNext modules for all shards in this client.
/// </summary>
/// <param name="client">Client to get CommandsNext instances from.</param>
/// <returns>A dictionary of the modules, indexed by shard id.</returns>
public static async Task<IReadOnlyDictionary<int, CommandsNextExtension>> GetCommandsNextAsync(this DiscordShardedClient client)
{
await client.InitializeShardsAsync().ConfigureAwait(false);
var extensions = new Dictionary<int, CommandsNextExtension>();
foreach (var shard in client.ShardClients.Select(xkvp => xkvp.Value))
{
extensions.Add(shard.ShardId, shard.GetExtension<CommandsNextExtension>());
}
return new ReadOnlyDictionary<int, CommandsNextExtension>(extensions);
}
/// <summary>
/// Registers all commands from a given assembly. The command classes need to be public to be considered for registration.
/// </summary>
/// <param name="extensions">Extensions to register commands on.</param>
/// <param name="assembly">Assembly to register commands from.</param>
public static void RegisterCommands(this IReadOnlyDictionary<int, CommandsNextExtension> extensions, Assembly assembly)
{
foreach (var extension in extensions.Values)
extension.RegisterCommands(assembly);
}
/// <summary>
/// Registers all commands from a given command class.
/// </summary>
/// <typeparam name="T">Class which holds commands to register.</typeparam>
/// <param name="extensions">Extensions to register commands on.</param>
public static void RegisterCommands<T>(this IReadOnlyDictionary<int, CommandsNextExtension> extensions) where T : BaseCommandModule
{
foreach (var extension in extensions.Values)
extension.RegisterCommands<T>();
}
/// <summary>
/// Registers all commands from a given command class.
/// </summary>
/// <param name="extensions">Extensions to register commands on.</param>
/// <param name="t">Type of the class which holds commands to register.</param>
public static void RegisterCommands(this IReadOnlyDictionary<int, CommandsNextExtension> extensions, Type t)
{
foreach (var extension in extensions.Values)
extension.RegisterCommands(t);
}
/// <summary>
/// Builds and registers all supplied commands.
/// </summary>
/// <param name="extensions">Extensions to register commands on.</param>
/// <param name="cmds">Commands to build and register.</param>
public static void RegisterCommands(this IReadOnlyDictionary<int, CommandsNextExtension> extensions, params CommandBuilder[] cmds)
{
foreach (var extension in extensions.Values)
extension.RegisterCommands(cmds);
}
/// <summary>
/// Unregisters specified commands from CommandsNext.
/// </summary>
/// <param name="extensions">Extensions to unregister commands on.</param>
/// <param name="cmds">Commands to unregister.</param>
public static void UnregisterCommands(this IReadOnlyDictionary<int, CommandsNextExtension> extensions, params Command[] cmds)
{
foreach (var extension in extensions.Values)
extension.UnregisterCommands(cmds);
}
/// <summary>
/// Registers an argument converter for specified type.
/// </summary>
/// <typeparam name="T">Type for which to register the converter.</typeparam>
/// <param name="extensions">Extensions to register the converter on.</param>
/// <param name="converter">Converter to register.</param>
public static void RegisterConverter<T>(this IReadOnlyDictionary<int, CommandsNextExtension> extensions, IArgumentConverter<T> converter)
{
foreach (var extension in extensions.Values)
extension.RegisterConverter(converter);
}
/// <summary>
/// Unregisters an argument converter for specified type.
/// </summary>
/// <typeparam name="T">Type for which to unregister the converter.</typeparam>
/// <param name="extensions">Extensions to unregister the converter on.</param>
public static void UnregisterConverter<T>(this IReadOnlyDictionary<int, CommandsNextExtension> extensions)
{
foreach (var extension in extensions.Values)
extension.UnregisterConverter<T>();
}
/// <summary>
/// Registers a user-friendly type name.
/// </summary>
/// <typeparam name="T">Type to register the name for.</typeparam>
/// <param name="extensions">Extensions to register the name on.</param>
/// <param name="value">Name to register.</param>
public static void RegisterUserFriendlyTypeName<T>(this IReadOnlyDictionary<int, CommandsNextExtension> extensions, string value)
{
foreach (var extension in extensions.Values)
extension.RegisterUserFriendlyTypeName<T>(value);
}
/// <summary>
/// Sets the help formatter to use with the default help command.
/// </summary>
/// <typeparam name="T">Type of the formatter to use.</typeparam>
/// <param name="extensions">Extensions to set the help formatter on.</param>
public static void SetHelpFormatter<T>(this IReadOnlyDictionary<int, CommandsNextExtension> extensions) where T : BaseHelpFormatter
{
foreach (var extension in extensions.Values)
extension.SetHelpFormatter<T>();
}
}
diff --git a/DisCatSharp.CommandsNext/GlobalSuppressions.cs b/DisCatSharp.CommandsNext/GlobalSuppressions.cs
index 8072fefed..910e70bc7 100644
--- a/DisCatSharp.CommandsNext/GlobalSuppressions.cs
+++ b/DisCatSharp.CommandsNext/GlobalSuppressions.cs
@@ -1,27 +1,27 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.CommandsNext.Attributes.RequireUserPermissionsAttribute.ExecuteCheckAsync(DisCatSharp.CommandsNext.CommandContext,System.Boolean)~System.Threading.Tasks.Task{System.Boolean}")]
[assembly: SuppressMessage("Performance", "CA1826:Do not use Enumerable methods on indexable collections", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.CommandsNext.Converters.DiscordMemberConverter.DisCatSharp#CommandsNext#Converters#IArgumentConverter<DisCatSharp#Entities#DiscordMember>#ConvertAsync(System.String,DisCatSharp.CommandsNext.CommandContext)~System.Threading.Tasks.Task{DisCatSharp.Entities.Optional{DisCatSharp.Entities.DiscordMember}}")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.CommandsNext.CommandsNextExtension.DefaultHelpModule.DefaultHelpAsync(DisCatSharp.CommandsNext.CommandContext,System.String[])~System.Threading.Tasks.Task")]
diff --git a/DisCatSharp.CommandsNext/Properties/AssemblyProperties.cs b/DisCatSharp.CommandsNext/Properties/AssemblyProperties.cs
index dc1b9e1bd..e53ea1406 100644
--- a/DisCatSharp.CommandsNext/Properties/AssemblyProperties.cs
+++ b/DisCatSharp.CommandsNext/Properties/AssemblyProperties.cs
@@ -1,46 +1,46 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("DisCatSharp.ApplicationCommands")]
[assembly: InternalsVisibleTo("DisCatSharp.Experimental")]
[assembly: InternalsVisibleTo("DisCatSharp")]
[assembly: InternalsVisibleTo("DisCatSharp.Common")]
[assembly: InternalsVisibleTo("DisCatSharp.Configuration")]
[assembly: InternalsVisibleTo("DisCatSharp.Configuration.Tests")]
[assembly: InternalsVisibleTo("DisCatSharp.Hosting")]
[assembly: InternalsVisibleTo("DisCatSharp.Hosting.DependencyInjection")]
[assembly: InternalsVisibleTo("DisCatSharp.Hosting.Tests")]
[assembly: InternalsVisibleTo("DisCatSharp.Interactivity")]
[assembly: InternalsVisibleTo("DisCatSharp.Lavalink")]
[assembly: InternalsVisibleTo("DisCatSharp.Phabricator")]
[assembly: InternalsVisibleTo("DisCatSharp.Support")]
[assembly: InternalsVisibleTo("DisCatSharp.Test")]
[assembly: InternalsVisibleTo("DisCatSharp.VoiceNext")]
[assembly: InternalsVisibleTo("DisCatSharp.VoiceNext.Natives")]
[assembly: InternalsVisibleTo("Nyaw")]
[assembly: InternalsVisibleTo("DisCatSharp.DevTools")]
[assembly: InternalsVisibleTo("DisCatSharp.DocsGenerator")]
[assembly: InternalsVisibleTo("DisCatSharp.StaffApps")]
[assembly: InternalsVisibleTo("Microsoft.DocAsCode")]
[assembly: InternalsVisibleTo("Microsoft.DocAsCode.Metadata.ManagedReference")]
diff --git a/DisCatSharp.Common/Attributes/DateTimeFormatAttribute.cs b/DisCatSharp.Common/Attributes/DateTimeFormatAttribute.cs
index adb5c3447..cc2e8b758 100644
--- a/DisCatSharp.Common/Attributes/DateTimeFormatAttribute.cs
+++ b/DisCatSharp.Common/Attributes/DateTimeFormatAttribute.cs
@@ -1,132 +1,132 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Defines the format for string-serialized <see cref="DateTime"/> and <see cref="DateTimeOffset"/> objects.
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class DateTimeFormatAttribute : SerializationAttribute
{
/// <summary>
/// Gets the ISO 8601 format string of "yyyy-MM-ddTHH:mm:ss.fffzzz".
/// </summary>
public const string FORMAT_ISO_8601 = "yyyy-MM-ddTHH:mm:ss.fffzzz";
/// <summary>
/// Gets the RFC 1123 format string of "R".
/// </summary>
public const string FORMAT_RFC_1123 = "R";
/// <summary>
/// Gets the general long format.
/// </summary>
public const string FORMAT_LONG = "G";
/// <summary>
/// Gets the general short format.
/// </summary>
public const string FORMAT_SHORT = "g";
/// <summary>
/// Gets the custom datetime format string to use.
/// </summary>
public string Format { get; }
/// <summary>
/// Gets the predefined datetime format kind.
/// </summary>
public DateTimeFormatKind Kind { get; }
/// <summary>
/// Specifies a predefined format to use.
/// </summary>
/// <param name="kind">Predefined format kind to use.</param>
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;
}
/// <summary>
/// <para>Specifies a custom format to use.</para>
/// <para>See https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings for more details.</para>
/// </summary>
/// <param name="format">Custom format string to use.</param>
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;
}
}
/// <summary>
/// <para>Defines which built-in format to use for for <see cref="DateTime"/> and <see cref="System.DateTimeOffset"/> serialization.</para>
/// <para>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.</para>
/// </summary>
public enum DateTimeFormatKind : int
{
/// <summary>
/// Specifies ISO 8601 format, which is equivalent to .NET format string of "yyyy-MM-ddTHH:mm:ss.fffzzz".
/// </summary>
ISO8601 = 0,
/// <summary>
/// Specifies RFC 1123 format, which is equivalent to .NET format string of "R".
/// </summary>
RFC1123 = 1,
/// <summary>
/// Specifies a format defined by <see cref="System.Globalization.CultureInfo.CurrentCulture"/>, with a format string of "G". This format is not recommended for portability reasons.
/// </summary>
CurrentLocaleLong = 2,
/// <summary>
/// Specifies a format defined by <see cref="System.Globalization.CultureInfo.CurrentCulture"/>, with a format string of "g". This format is not recommended for portability reasons.
/// </summary>
CurrentLocaleShort = 3,
/// <summary>
/// Specifies a format defined by <see cref="System.Globalization.CultureInfo.InvariantCulture"/>, with a format string of "G".
/// </summary>
InvariantLocaleLong = 4,
/// <summary>
/// Specifies a format defined by <see cref="System.Globalization.CultureInfo.InvariantCulture"/>, with a format string of "g".
/// </summary>
InvariantLocaleShort = 5,
/// <summary>
/// Specifies a custom format. This value is not usable directly.
/// </summary>
Custom = int.MaxValue
}
diff --git a/DisCatSharp.Common/Attributes/DecomposerAttribute.cs b/DisCatSharp.Common/Attributes/DecomposerAttribute.cs
index 49503b638..b0ed6b87d 100644
--- a/DisCatSharp.Common/Attributes/DecomposerAttribute.cs
+++ b/DisCatSharp.Common/Attributes/DecomposerAttribute.cs
@@ -1,49 +1,49 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Common.Serialization;
/// <summary>
/// Specifies a decomposer for a given type or property.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field)]
public sealed class DecomposerAttribute : SerializationAttribute
{
/// <summary>
/// Gets the type of the decomposer.
/// </summary>
public Type DecomposerType { get; }
/// <summary>
/// Specifies a decomposer for given type or property.
/// </summary>
/// <param name="type">Type of decomposer to use.</param>
public DecomposerAttribute(Type type)
{
if (!typeof(IDecomposer).IsAssignableFrom(type) || !type.IsClass || type.IsAbstract) // abstract covers static - static = abstract + sealed
throw new ArgumentException("Invalid type specified. Must be a non-abstract class which implements DisCatSharp.Common.Serialization.IDecomposer interface.", nameof(type));
this.DecomposerType = type;
}
}
diff --git a/DisCatSharp.Common/Attributes/EnumAttributes.cs b/DisCatSharp.Common/Attributes/EnumAttributes.cs
index b52b2cad2..6ebdd416a 100644
--- a/DisCatSharp.Common/Attributes/EnumAttributes.cs
+++ b/DisCatSharp.Common/Attributes/EnumAttributes.cs
@@ -1,41 +1,41 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Common.Serialization;
/// <summary>
/// <para>Specifies that this enum should be serialized and deserialized as its underlying numeric type.</para>
/// <para>This is used to change the behaviour of enum serialization.</para>
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class NumericEnumAttribute : SerializationAttribute
{ }
/// <summary>
/// <para>Specifies that this enum should be serialized and deserialized as its string representation.</para>
/// <para>This is used to change the behaviour of enum serialization.</para>
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class StringEnumAttribute : SerializationAttribute
{ }
diff --git a/DisCatSharp.Common/Attributes/IncludeNullAttribute.cs b/DisCatSharp.Common/Attributes/IncludeNullAttribute.cs
index 3738e8af3..58618e0f0 100644
--- a/DisCatSharp.Common/Attributes/IncludeNullAttribute.cs
+++ b/DisCatSharp.Common/Attributes/IncludeNullAttribute.cs
@@ -1,34 +1,34 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Common.Serialization;
/// <summary>
/// <para>Specifies that if the value of the field or property is null, it should be included in the serialized data.</para>
/// <para>This alters the default behaviour of ignoring nulls.</para>
/// </summary>
[Obsolete("Use [DataMember] with EmitDefaultValue = true.")]
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class IncludeNullAttribute : SerializationAttribute
{ }
diff --git a/DisCatSharp.Common/Attributes/Int53Attribute.cs b/DisCatSharp.Common/Attributes/Int53Attribute.cs
index e3f440c28..fe3527862 100644
--- a/DisCatSharp.Common/Attributes/Int53Attribute.cs
+++ b/DisCatSharp.Common/Attributes/Int53Attribute.cs
@@ -1,45 +1,45 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Common.Serialization;
/// <summary>
/// <para>Specifies that this 64-bit integer uses no more than 53 bits to represent its value.</para>
/// <para>This is used to indicate that large numbers are safe for direct serialization into formats which do support 64-bit integers natively (such as JSON).</para>
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class Int53Attribute : SerializationAttribute
{
/// <summary>
/// <para>Gets the maximum safe value representable as an integer by a IEEE754 64-bit binary floating point value.</para>
/// <para>This value equals to 9007199254740991.</para>
/// </summary>
public const long MAX_VALUE = (1L << 53) - 1;
/// <summary>
/// <para>Gets the minimum safe value representable as an integer by a IEEE754 64-bit binary floating point value.</para>
/// <para>This value equals to -9007199254740991.</para>
/// </summary>
public const long MIN_VALUE = -MAX_VALUE;
}
diff --git a/DisCatSharp.Common/Attributes/SerializationAttribute.cs b/DisCatSharp.Common/Attributes/SerializationAttribute.cs
index 86e1f7c6f..3b9456664 100644
--- a/DisCatSharp.Common/Attributes/SerializationAttribute.cs
+++ b/DisCatSharp.Common/Attributes/SerializationAttribute.cs
@@ -1,31 +1,31 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Common.Serialization;
/// <summary>
/// ABC for serialization attributes.
/// </summary>
public abstract class SerializationAttribute : Attribute
{ }
diff --git a/DisCatSharp.Common/Attributes/SerializedNameAttribute.cs b/DisCatSharp.Common/Attributes/SerializedNameAttribute.cs
index 4411d8be2..49242c87d 100644
--- a/DisCatSharp.Common/Attributes/SerializedNameAttribute.cs
+++ b/DisCatSharp.Common/Attributes/SerializedNameAttribute.cs
@@ -1,47 +1,47 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Common.Serialization;
/// <summary>
/// Declares name of a property in serialized data. This is used for mapping serialized data to object properties and fields.
/// </summary>
[Obsolete("Use [DataMember] with set Name instead.")]
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class SerializedNameAttribute : SerializationAttribute
{
/// <summary>
/// Gets the serialized name of the field or property.
/// </summary>
public string Name { get; }
/// <summary>
/// Declares name of a property in serialized data. This is used for mapping serialized data to object properties and fields.
/// </summary>
/// <param name="name">Name of the field or property in serialized data.</param>
public SerializedNameAttribute(string name)
{
this.Name = name;
}
}
diff --git a/DisCatSharp.Common/Attributes/TimeSpanAttributes.cs b/DisCatSharp.Common/Attributes/TimeSpanAttributes.cs
index b5b123fba..6bd6ad7a5 100644
--- a/DisCatSharp.Common/Attributes/TimeSpanAttributes.cs
+++ b/DisCatSharp.Common/Attributes/TimeSpanAttributes.cs
@@ -1,41 +1,41 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Common.Serialization;
/// <summary>
/// <para>Specifies that this <see cref="TimeSpan"/> will be serialized as a number of whole seconds.</para>
/// <para>This value will always be serialized as a number.</para>
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class TimeSpanSecondsAttribute : SerializationAttribute
{ }
/// <summary>
/// <para>Specifies that this <see cref="TimeSpan"/> will be serialized as a number of whole milliseconds.</para>
/// <para>This value will always be serialized as a number.</para>
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class TimeSpanMillisecondsAttribute : SerializationAttribute
{ }
diff --git a/DisCatSharp.Common/Attributes/TimeSpanFormatAttribute.cs b/DisCatSharp.Common/Attributes/TimeSpanFormatAttribute.cs
index 850614912..c06563d84 100644
--- a/DisCatSharp.Common/Attributes/TimeSpanFormatAttribute.cs
+++ b/DisCatSharp.Common/Attributes/TimeSpanFormatAttribute.cs
@@ -1,132 +1,132 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Defines the format for string-serialized <see cref="TimeSpan"/> objects.
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class TimeSpanFormatAttribute : SerializationAttribute
{
/// <summary>
/// Gets the ISO 8601 format string of @"ddThh\:mm\:ss\.fff".
/// </summary>
public const string FORMAT_ISO_8601 = @"ddThh\:mm\:ss\.fff";
/// <summary>
/// Gets the constant format.
/// </summary>
public const string FORMAT_CONSTANT = "c";
/// <summary>
/// Gets the general long format.
/// </summary>
public const string FORMAT_LONG = "G";
/// <summary>
/// Gets the general short format.
/// </summary>
public const string FORMAT_SHORT = "g";
/// <summary>
/// Gets the custom datetime format string to use.
/// </summary>
public string Format { get; }
/// <summary>
/// Gets the predefined datetime format kind.
/// </summary>
public TimeSpanFormatKind Kind { get; }
/// <summary>
/// Specifies a predefined format to use.
/// </summary>
/// <param name="kind">Predefined format kind to use.</param>
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;
}
/// <summary>
/// <para>Specifies a custom format to use.</para>
/// <para>See https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-timespan-format-strings for more details.</para>
/// </summary>
/// <param name="format">Custom format string to use.</param>
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;
}
}
/// <summary>
/// <para>Defines which built-in format to use for <see cref="TimeSpan"/> serialization.</para>
/// <para>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.</para>
/// </summary>
public enum TimeSpanFormatKind : int
{
/// <summary>
/// Specifies ISO 8601-like time format, which is equivalent to .NET format string of @"ddThh\:mm\:ss\.fff".
/// </summary>
ISO8601 = 0,
/// <summary>
/// Specifies a format defined by <see cref="System.Globalization.CultureInfo.InvariantCulture"/>, with a format string of "c".
/// </summary>
InvariantConstant = 1,
/// <summary>
/// Specifies a format defined by <see cref="System.Globalization.CultureInfo.CurrentCulture"/>, with a format string of "G". This format is not recommended for portability reasons.
/// </summary>
CurrentLocaleLong = 2,
/// <summary>
/// Specifies a format defined by <see cref="System.Globalization.CultureInfo.CurrentCulture"/>, with a format string of "g". This format is not recommended for portability reasons.
/// </summary>
CurrentLocaleShort = 3,
/// <summary>
/// Specifies a format defined by <see cref="System.Globalization.CultureInfo.InvariantCulture"/>, with a format string of "G". This format is not recommended for portability reasons.
/// </summary>
InvariantLocaleLong = 4,
/// <summary>
/// Specifies a format defined by <see cref="System.Globalization.CultureInfo.InvariantCulture"/>, with a format string of "g". This format is not recommended for portability reasons.
/// </summary>
InvariantLocaleShort = 5,
/// <summary>
/// Specifies a custom format. This value is not usable directly.
/// </summary>
Custom = int.MaxValue
}
diff --git a/DisCatSharp.Common/Attributes/UnixTimestampAttributes.cs b/DisCatSharp.Common/Attributes/UnixTimestampAttributes.cs
index 32144740a..623d7b0ff 100644
--- a/DisCatSharp.Common/Attributes/UnixTimestampAttributes.cs
+++ b/DisCatSharp.Common/Attributes/UnixTimestampAttributes.cs
@@ -1,41 +1,41 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Common.Serialization;
/// <summary>
/// <para>Specifies that this <see cref="DateTime"/> or <see cref="DateTimeOffset"/> will be serialized as Unix timestamp seconds.</para>
/// <para>This value will always be serialized as a number.</para>
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class UnixSecondsAttribute : SerializationAttribute
{ }
/// <summary>
/// <para>Specifies that this <see cref="DateTime"/> or <see cref="DateTimeOffset"/> will be serialized as Unix timestamp milliseconds.</para>
/// <para>This value will always be serialized as a number.</para>
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class UnixMillisecondsAttribute : SerializationAttribute
{ }
diff --git a/DisCatSharp.Common/GlobalSuppressions.cs b/DisCatSharp.Common/GlobalSuppressions.cs
index 1839745ae..9b5b3a999 100644
--- a/DisCatSharp.Common/GlobalSuppressions.cs
+++ b/DisCatSharp.Common/GlobalSuppressions.cs
@@ -1,69 +1,69 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Style", "IDE0090:Use 'new(...)'", Justification = "<Pending>", Scope = "member", Target = "~F:DisCatSharp.Common.Utilities.AsyncEvent`2._lock")]
[assembly: SuppressMessage("Style", "IDE0022:Use expression body for methods", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.CharSpanLookupDictionary`1.Enumerator.Dispose")]
[assembly: SuppressMessage("Style", "IDE0083:Use pattern matching", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.CharSpanLookupDictionary`1.System#Collections#IDictionary#Add(System.Object,System.Object)")]
[assembly: SuppressMessage("Style", "IDE0083:Use pattern matching", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.CharSpanLookupDictionary`1.System#Collections#IDictionary#Contains(System.Object)~System.Boolean")]
[assembly: SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.CharSpanLookupDictionary`1.System#Collections#IDictionary#Contains(System.Object)~System.Boolean")]
[assembly: SuppressMessage("Style", "IDE0083:Use pattern matching", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.CharSpanLookupDictionary`1.System#Collections#IDictionary#Remove(System.Object)")]
[assembly: SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.CharSpanLookupDictionary`1.TryGetValue(System.String,`0@)~System.Boolean")]
[assembly: SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.CharSpanLookupDictionary`1.TryRemove(System.String,`0@)~System.Boolean")]
[assembly: SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.Extensions.IsInRange(System.Double,System.Double,System.Double,System.Boolean)~System.Boolean")]
[assembly: SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.Extensions.IsInRange(System.Single,System.Single,System.Single,System.Boolean)~System.Boolean")]
[assembly: SuppressMessage("Style", "IDE0047:Remove unnecessary parentheses", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.Types.ContinuousMemoryBuffer`1.Read(System.Span{`0},System.UInt64,System.Int32@)~System.Boolean")]
[assembly: SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.Types.ContinuousMemoryBuffer`1.ToArray~`0[]")]
[assembly: SuppressMessage("Style", "IDE0022:Use expression body for methods", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.Utilities.AsyncEvent`2.UnregisterAll")]
[assembly: SuppressMessage("Style", "IDE0090:Use 'new(...)'", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.Common.CharSpanLookupDictionary`1.Enumerator.System#Collections#IDictionaryEnumerator#Entry")]
[assembly: SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.Common.CharSpanLookupDictionary`1.Item(System.ReadOnlySpan{System.Char})")]
[assembly: SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.Common.CharSpanLookupDictionary`1.Item(System.String)")]
[assembly: SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.Common.CharSpanLookupDictionary`1.System#Collections#IDictionary#Item(System.Object)")]
[assembly: SuppressMessage("Style", "IDE0083:Use pattern matching", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.Common.CharSpanLookupDictionary`1.System#Collections#IDictionary#Item(System.Object)")]
[assembly: SuppressMessage("Style", "IDE0022:Use expression body for methods", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.CharSpanLookupReadOnlyDictionary`1.Enumerator.Dispose")]
[assembly: SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.CharSpanLookupReadOnlyDictionary`1.TryGetValue(System.String,`0@)~System.Boolean")]
[assembly: SuppressMessage("Style", "IDE0022:Use expression body for methods", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.SecureRandom.GetBytes(System.Span{System.Byte})")]
[assembly: SuppressMessage("Style", "IDE0022:Use expression body for methods", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.SecureRandom.GetNonZeroBytes(System.Span{System.Byte})")]
[assembly: SuppressMessage("Style", "IDE0047:Remove unnecessary parentheses", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.Types.MemoryBuffer`1.Grow(System.Int32)")]
[assembly: SuppressMessage("Style", "IDE0047:Remove unnecessary parentheses", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.Types.MemoryBuffer`1.Read(System.Span{`0},System.UInt64,System.Int32@)~System.Boolean")]
[assembly: SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.Common.CharSpanLookupReadOnlyDictionary`1.Item(System.ReadOnlySpan{System.Char})")]
[assembly: SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.Common.CharSpanLookupReadOnlyDictionary`1.Item(System.String)")]
[assembly: SuppressMessage("Style", "IDE0022:Use expression body for methods", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.SecureRandom.GetBytes(System.Byte[])")]
[assembly: SuppressMessage("Style", "IDE0048:Add parentheses for clarity", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.SecureRandom.GetInt16(System.Int16,System.Int16)~System.Int16")]
[assembly: SuppressMessage("Style", "IDE0048:Add parentheses for clarity", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.SecureRandom.GetInt32(System.Int32,System.Int32)~System.Int32")]
[assembly: SuppressMessage("Style", "IDE0048:Add parentheses for clarity", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.SecureRandom.GetInt64(System.Int64,System.Int64)~System.Int64")]
[assembly: SuppressMessage("Style", "IDE0048:Add parentheses for clarity", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.SecureRandom.GetInt8(System.SByte,System.SByte)~System.SByte")]
[assembly: SuppressMessage("Style", "IDE0022:Use expression body for methods", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.SecureRandom.GetNonZeroBytes(System.Byte[])")]
[assembly: SuppressMessage("Style", "IDE0048:Add parentheses for clarity", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.SecureRandom.GetUInt16(System.UInt16,System.UInt16)~System.UInt16")]
[assembly: SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.SecureRandom.GetUInt16(System.UInt16,System.UInt16)~System.UInt16")]
[assembly: SuppressMessage("Style", "IDE0048:Add parentheses for clarity", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.SecureRandom.GetUInt32(System.UInt32,System.UInt32)~System.UInt32")]
[assembly: SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.SecureRandom.GetUInt32(System.UInt32,System.UInt32)~System.UInt32")]
[assembly: SuppressMessage("Style", "IDE0048:Add parentheses for clarity", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.SecureRandom.GetUInt64(System.UInt64,System.UInt64)~System.UInt64")]
[assembly: SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.SecureRandom.GetUInt64(System.UInt64,System.UInt64)~System.UInt64")]
[assembly: SuppressMessage("Style", "IDE0048:Add parentheses for clarity", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.SecureRandom.GetUInt8(System.Byte,System.Byte)~System.Byte")]
[assembly: SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.SecureRandom.GetUInt8(System.Byte,System.Byte)~System.Byte")]
[assembly: SuppressMessage("Style", "IDE0045:Convert to conditional expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.Utilities.AsyncExecutor.Execute(System.Threading.Tasks.Task)")]
[assembly: SuppressMessage("Style", "IDE0062:Make local function 'static'", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.Utilities.AsyncExecutor.Execute(System.Threading.Tasks.Task)")]
[assembly: SuppressMessage("Style", "IDE0045:Convert to conditional expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.Utilities.AsyncExecutor.Execute``1(System.Threading.Tasks.Task{``0})~``0")]
[assembly: SuppressMessage("Style", "IDE0062:Make local function 'static'", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.Utilities.AsyncExecutor.Execute``1(System.Threading.Tasks.Task{``0})~``0")]
[assembly: SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Common.Utilities.ReflectionUtilities.ToDictionary``1(``0)~System.Collections.Generic.IReadOnlyDictionary{System.String,System.Object}")]
diff --git a/DisCatSharp.Common/Properties/AssemblyProperties.cs b/DisCatSharp.Common/Properties/AssemblyProperties.cs
index 36ebee4ec..afff6029f 100644
--- a/DisCatSharp.Common/Properties/AssemblyProperties.cs
+++ b/DisCatSharp.Common/Properties/AssemblyProperties.cs
@@ -1,46 +1,46 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("DisCatSharp.ApplicationCommands")]
[assembly: InternalsVisibleTo("DisCatSharp.CommandsNext")]
[assembly: InternalsVisibleTo("DisCatSharp.Experimental")]
[assembly: InternalsVisibleTo("DisCatSharp")]
[assembly: InternalsVisibleTo("DisCatSharp.Configuration")]
[assembly: InternalsVisibleTo("DisCatSharp.Configuration.Tests")]
[assembly: InternalsVisibleTo("DisCatSharp.Hosting")]
[assembly: InternalsVisibleTo("DisCatSharp.Hosting.DependencyInjection")]
[assembly: InternalsVisibleTo("DisCatSharp.Hosting.Tests")]
[assembly: InternalsVisibleTo("DisCatSharp.Interactivity")]
[assembly: InternalsVisibleTo("DisCatSharp.Lavalink")]
[assembly: InternalsVisibleTo("DisCatSharp.Phabricator")]
[assembly: InternalsVisibleTo("DisCatSharp.Support")]
[assembly: InternalsVisibleTo("DisCatSharp.Test")]
[assembly: InternalsVisibleTo("DisCatSharp.VoiceNext")]
[assembly: InternalsVisibleTo("DisCatSharp.VoiceNext.Natives")]
[assembly: InternalsVisibleTo("Nyaw")]
[assembly: InternalsVisibleTo("DisCatSharp.DevTools")]
[assembly: InternalsVisibleTo("DisCatSharp.DocsGenerator")]
[assembly: InternalsVisibleTo("DisCatSharp.StaffApps")]
[assembly: InternalsVisibleTo("Microsoft.DocAsCode")]
[assembly: InternalsVisibleTo("Microsoft.DocAsCode.Metadata.ManagedReference")]
diff --git a/DisCatSharp.Common/RegularExpressions/CommonRegEx.cs b/DisCatSharp.Common/RegularExpressions/CommonRegEx.cs
index 4ae0c76c2..4d79f4996 100644
--- a/DisCatSharp.Common/RegularExpressions/CommonRegEx.cs
+++ b/DisCatSharp.Common/RegularExpressions/CommonRegEx.cs
@@ -1,74 +1,74 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Text.RegularExpressions;
namespace DisCatSharp.Common.RegularExpressions;
/// <summary>
/// Provides common regex.
/// </summary>
public static class CommonRegEx
{
/// <summary>
/// Represents a hex color string.
/// </summary>
public static Regex HexColorString
=> new(@"^#?([a-fA-F0-9]{6})$", RegexOptions.ECMAScript | RegexOptions.Compiled);
/// <summary>
/// Represents a rgb color string.
/// </summary>
public static Regex RgbColorString
=> new(@"^(\d{1,3})\s*?,\s*?(\d{1,3}),\s*?(\d{1,3})$", RegexOptions.ECMAScript | RegexOptions.Compiled);
/// <summary>
/// Represents a timespan.
/// </summary>
public static Regex TimeSpan
=> new(@"^(?<days>\d+d\s*)?(?<hours>\d{1,2}h\s*)?(?<minutes>\d{1,2}m\s*)?(?<seconds>\d{1,2}s\s*)?$", RegexOptions.ECMAScript | RegexOptions.Compiled);
/// <summary>
/// Represents a advanced youtube regex.
/// Named groups:
/// <list type="table">
/// <listheader>
/// <term>group</term>
/// <description>description</description>
/// </listheader>
/// <item>
/// <term>id</term>
/// <description>Video ID</description>
/// </item>
/// <item>
/// <term>list</term>
/// <description>List ID</description>
/// </item>
/// <item>
/// <term>index</term>
/// <description>List index</description>
/// </item>
/// </list>
/// </summary>
public static Regex AdvancedYoutubeRegex
=> new(@"http(s)?:\/\/(www\.)?youtu(\.be|be\.com)\/(watch\?v=|playlist)?(?<id>\w{1,})?((\?|\&)list=(?<list>\w{1,}))(&index=(?<index>\d{1,}))?", RegexOptions.ECMAScript | RegexOptions.Compiled);
}
diff --git a/DisCatSharp.Common/RegularExpressions/DiscordRegEx.cs b/DisCatSharp.Common/RegularExpressions/DiscordRegEx.cs
index ca38fbad0..aa428a185 100644
--- a/DisCatSharp.Common/RegularExpressions/DiscordRegEx.cs
+++ b/DisCatSharp.Common/RegularExpressions/DiscordRegEx.cs
@@ -1,124 +1,124 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Text.RegularExpressions;
namespace DisCatSharp.Common.RegularExpressions;
/// <summary>
/// Provides common regex for discord related things.
/// </summary>
public static class DiscordRegEx
{
// language=regex
private const string WEBSITE = @"(https?:\/\/)?(www\.|canary\.|ptb\.)?(discord|discordapp)\.com\/";
/// <summary>
/// Represents a invite.
/// </summary>
public static readonly Regex Invite
= new($@"^((https?:\/\/)?(www\.)?discord\.gg(\/.*)*|{WEBSITE}invite)\/(?<code>[a-zA-Z0-9]*)(\?event=(?<event>\d+))?$", RegexOptions.ECMAScript | RegexOptions.Compiled);
/// <summary>
/// Represents a message link.
/// </summary>
public static readonly Regex MessageLink
= new($@"^{WEBSITE}channels\/(?<guild>(?:\d+|@me))\/(?<channel>\d+)\/(?<message>\d+)\/?", RegexOptions.ECMAScript | RegexOptions.Compiled);
/// <summary>
/// Represents a emoji.
/// </summary>
public static readonly Regex Emoji
= new(@"^<(?<animated>a)?:(?<name>[a-zA-Z0-9_]+?):(?<id>\d+?)>$", RegexOptions.ECMAScript | RegexOptions.Compiled);
/// <summary>
/// Represents a animated emoji.
/// </summary>
public static readonly Regex AnimatedEmoji
= new(@"^<(?<animated>a):(?<name>\w{2,32}):(?<id>\d{17,20})>$", RegexOptions.ECMAScript | RegexOptions.Compiled);
/// <summary>
/// Represents a non-animated emoji.
/// </summary>
public static readonly Regex StaticEmoji
= new(@"^<:(?<name>\w{2,32}):(?<id>\d{17,20})>$", RegexOptions.ECMAScript | RegexOptions.Compiled);
/// <summary>
/// Represents a timestamp.
/// </summary>
public static readonly Regex Timestamp
= new(@"^<t:(?<timestamp>-?\d{1,13})(:(?<style>[tTdDfFR]))?>$", RegexOptions.ECMAScript | RegexOptions.Compiled);
/// <summary>
/// Represents a default styled timestamp.
/// </summary>
public static readonly Regex DefaultStyledTimestamp
= new(@"^<t:(?<timestamp>-?\d{1,13})$", RegexOptions.ECMAScript | RegexOptions.Compiled);
/// <summary>
/// Represents a styled timestamp.
/// </summary>
public static readonly Regex StyledTimestamp
= new(@"^<t:(?<timestamp>-?\d{1,13}):(?<style>[tTdDfFR])>$", RegexOptions.ECMAScript | RegexOptions.Compiled);
/// <summary>
/// Represents a role.
/// </summary>
public static readonly Regex Role
= new(@"^<@&(\d+?)>$", RegexOptions.ECMAScript | RegexOptions.Compiled);
/// <summary>
/// Represents a channel.
/// </summary>
public static readonly Regex Channel
= new(@"^<#(\d+)>$", RegexOptions.ECMAScript | RegexOptions.Compiled);
/// <summary>
/// Represents a user.
/// </summary>
public static readonly Regex User
= new(@"^<@\!?(\d+?)>$", RegexOptions.ECMAScript | RegexOptions.Compiled);
/// <summary>
/// Represents a user with nickname.
/// </summary>
public static readonly Regex UserWithNickname
= new(@"^<@\!(?<id>\d{17,20})>$", RegexOptions.ECMAScript | RegexOptions.Compiled);
/// <summary>
/// Represents a user with optional nickname.
/// </summary>
public static readonly Regex UserWithOptionalNickname
= new(@"^<@\!?(?<id>\d{17,20})>$", RegexOptions.ECMAScript | RegexOptions.Compiled);
/// <summary>
/// Represents a user without nickname.
/// </summary>
public static readonly Regex UserWithoutNickname
= new(@"^<@(?<id>\d{17,20})>$", RegexOptions.ECMAScript | RegexOptions.Compiled);
/// <summary>
/// Represents a event.
/// </summary>
public static readonly Regex Event
= new($@"^{WEBSITE}events\/(?<guild>\d+)\/(?<event>\d+)$", RegexOptions.ECMAScript | RegexOptions.Compiled);
}
diff --git a/DisCatSharp.Common/Types/CharSpanLookupDictionary.cs b/DisCatSharp.Common/Types/CharSpanLookupDictionary.cs
index 75e2141ea..3436c9899 100644
--- a/DisCatSharp.Common/Types/CharSpanLookupDictionary.cs
+++ b/DisCatSharp.Common/Types/CharSpanLookupDictionary.cs
@@ -1,813 +1,813 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Collections.Immutable;
namespace DisCatSharp.Common;
/// <summary>
/// Represents collection of string keys and <typeparamref name="TValue"/> values, allowing the use of <see cref="ReadOnlySpan{T}"/> for dictionary operations.
/// </summary>
/// <typeparam name="TValue">Type of items in this dictionary.</typeparam>
public sealed class CharSpanLookupDictionary<TValue> :
IDictionary<string, TValue>,
IReadOnlyDictionary<string, TValue>,
IDictionary
{
/// <summary>
/// Gets the collection of all keys present in this dictionary.
/// </summary>
public IEnumerable<string> Keys => this.GetKeysInternal();
/// <summary>
/// Gets the keys.
/// </summary>
ICollection<string> IDictionary<string, TValue>.Keys => this.GetKeysInternal();
/// <summary>
/// Gets the keys.
/// </summary>
ICollection IDictionary.Keys => this.GetKeysInternal();
/// <summary>
/// Gets the collection of all values present in this dictionary.
/// </summary>
public IEnumerable<TValue> Values => this.GetValuesInternal();
/// <summary>
/// Gets the values.
/// </summary>
ICollection<TValue> IDictionary<string, TValue>.Values => this.GetValuesInternal();
/// <summary>
/// Gets the values.
/// </summary>
ICollection IDictionary.Values => this.GetValuesInternal();
/// <summary>
/// Gets the total number of items in this dictionary.
/// </summary>
public int Count { get; private set; }
/// <summary>
/// Gets whether this dictionary is read-only.
/// </summary>
public bool IsReadOnly => false;
/// <summary>
/// Gets whether this dictionary has a fixed size.
/// </summary>
public bool IsFixedSize => false;
/// <summary>
/// Gets whether this dictionary is considered thread-safe.
/// </summary>
public bool IsSynchronized => false;
/// <summary>
/// Gets the object which allows synchronizing access to this dictionary.
/// </summary>
public object SyncRoot { get; } = new();
/// <summary>
/// Gets or sets a value corresponding to given key in this dictionary.
/// </summary>
/// <param name="key">Key to get or set the value for.</param>
/// <returns>Value matching the supplied key, if applicable.</returns>
public TValue this[string key]
{
get
{
if (key == null)
throw new ArgumentNullException(nameof(key));
if (!this.TryRetrieveInternal(key.AsSpan(), out var value))
throw new KeyNotFoundException($"The given key '{key}' was not present in the dictionary.");
return value;
}
set
{
if (key == null)
throw new ArgumentNullException(nameof(key));
this.TryInsertInternal(key, value, true);
}
}
/// <summary>
/// Gets or sets a value corresponding to given key in this dictionary.
/// </summary>
/// <param name="key">Key to get or set the value for.</param>
/// <returns>Value matching the supplied key, if applicable.</returns>
public TValue this[ReadOnlySpan<char> key]
{
get
{
if (!this.TryRetrieveInternal(key, out var value))
throw new KeyNotFoundException($"The given key was not present in the dictionary.");
return value;
}
set
{
unsafe
{
fixed (char* chars = &key.GetPinnableReference())
this.TryInsertInternal(new string(chars, 0, key.Length), value, true);
}
}
}
object IDictionary.this[object key]
{
get
{
if (!(key is string tkey))
throw new ArgumentException("Key needs to be an instance of a string.");
if (!this.TryRetrieveInternal(tkey.AsSpan(), out var value))
throw new KeyNotFoundException($"The given key '{tkey}' was not present in the dictionary.");
return value;
}
set
{
if (!(key is string tkey))
throw new ArgumentException("Key needs to be an instance of a string.");
if (!(value is TValue tvalue))
{
tvalue = default;
if (tvalue != null)
throw new ArgumentException($"Value needs to be an instance of {typeof(TValue)}.");
}
this.TryInsertInternal(tkey, tvalue, true);
}
}
/// <summary>
/// Gets the internal buckets.
/// </summary>
private readonly Dictionary<ulong, KeyedValue> _internalBuckets;
/// <summary>
/// Creates a new, empty <see cref="CharSpanLookupDictionary{TValue}"/> with string keys and items of type <typeparamref name="TValue"/>.
/// </summary>
public CharSpanLookupDictionary()
{
this._internalBuckets = new Dictionary<ulong, KeyedValue>();
}
/// <summary>
/// Creates a new, empty <see cref="CharSpanLookupDictionary{TValue}"/> with string keys and items of type <typeparamref name="TValue"/> and sets its initial capacity to specified value.
/// </summary>
/// <param name="initialCapacity">Initial capacity of the dictionary.</param>
public CharSpanLookupDictionary(int initialCapacity)
{
this._internalBuckets = new Dictionary<ulong, KeyedValue>(initialCapacity);
}
/// <summary>
/// Creates a new <see cref="CharSpanLookupDictionary{TValue}"/> with string keys and items of type <typeparamref name="TValue"/> and populates it with key-value pairs from supplied dictionary.
/// </summary>
/// <param name="values">Dictionary containing items to populate this dictionary with.</param>
public CharSpanLookupDictionary(IDictionary<string, TValue> values)
: this(values.Count)
{
foreach (var (k, v) in values)
this.Add(k, v);
}
/// <summary>
/// Creates a new <see cref="CharSpanLookupDictionary{TValue}"/> with string keys and items of type <typeparamref name="TValue"/> and populates it with key-value pairs from supplied dictionary.
/// </summary>
/// <param name="values">Dictionary containing items to populate this dictionary with.</param>
public CharSpanLookupDictionary(IReadOnlyDictionary<string, TValue> values)
: this(values.Count)
{
foreach (var (k, v) in values)
this.Add(k, v);
}
/// <summary>
/// Creates a new <see cref="CharSpanLookupDictionary{TValue}"/> with string keys and items of type <typeparamref name="TValue"/> and populates it with key-value pairs from supplied key-value collection.
/// </summary>
/// <param name="values">Dictionary containing items to populate this dictionary with.</param>
public CharSpanLookupDictionary(IEnumerable<KeyValuePair<string, TValue>> values)
: this()
{
foreach (var (k, v) in values)
this.Add(k, v);
}
/// <summary>
/// Inserts a specific key and corresponding value into this dictionary.
/// </summary>
/// <param name="key">Key to insert.</param>
/// <param name="value">Value corresponding to this key.</param>
public void Add(string key, TValue value)
{
if (!this.TryInsertInternal(key, value, false))
throw new ArgumentException("Given key is already present in the dictionary.", nameof(key));
}
/// <summary>
/// Inserts a specific key and corresponding value into this dictionary.
/// </summary>
/// <param name="key">Key to insert.</param>
/// <param name="value">Value corresponding to this key.</param>
public void Add(ReadOnlySpan<char> key, TValue value)
{
unsafe
{
fixed (char* chars = &key.GetPinnableReference())
if (!this.TryInsertInternal(new string(chars, 0, key.Length), value, false))
throw new ArgumentException("Given key is already present in the dictionary.", nameof(key));
}
}
/// <summary>
/// Attempts to insert a specific key and corresponding value into this dictionary.
/// </summary>
/// <param name="key">Key to insert.</param>
/// <param name="value">Value corresponding to this key.</param>
/// <returns>Whether the operation was successful.</returns>
public bool TryAdd(string key, TValue value)
=> this.TryInsertInternal(key, value, false);
/// <summary>
/// Attempts to insert a specific key and corresponding value into this dictionary.
/// </summary>
/// <param name="key">Key to insert.</param>
/// <param name="value">Value corresponding to this key.</param>
/// <returns>Whether the operation was successful.</returns>
public bool TryAdd(ReadOnlySpan<char> key, TValue value)
{
unsafe
{
fixed (char* chars = &key.GetPinnableReference())
return this.TryInsertInternal(new string(chars, 0, key.Length), value, false);
}
}
/// <summary>
/// Attempts to retrieve a value corresponding to the supplied key from this dictionary.
/// </summary>
/// <param name="key">Key to retrieve the value for.</param>
/// <param name="value">Retrieved value.</param>
/// <returns>Whether the operation was successful.</returns>
public bool TryGetValue(string key, out TValue value)
{
if (key == null)
throw new ArgumentNullException(nameof(key));
return this.TryRetrieveInternal(key.AsSpan(), out value);
}
/// <summary>
/// Attempts to retrieve a value corresponding to the supplied key from this dictionary.
/// </summary>
/// <param name="key">Key to retrieve the value for.</param>
/// <param name="value">Retrieved value.</param>
/// <returns>Whether the operation was successful.</returns>
public bool TryGetValue(ReadOnlySpan<char> key, out TValue value)
=> this.TryRetrieveInternal(key, out value);
/// <summary>
/// Attempts to remove a value corresponding to the supplied key from this dictionary.
/// </summary>
/// <param name="key">Key to remove the value for.</param>
/// <param name="value">Removed value.</param>
/// <returns>Whether the operation was successful.</returns>
public bool TryRemove(string key, out TValue value)
{
if (key == null)
throw new ArgumentNullException(nameof(key));
return this.TryRemoveInternal(key.AsSpan(), out value);
}
/// <summary>
/// Attempts to remove a value corresponding to the supplied key from this dictionary.
/// </summary>
/// <param name="key">Key to remove the value for.</param>
/// <param name="value">Removed value.</param>
/// <returns>Whether the operation was successful.</returns>
public bool TryRemove(ReadOnlySpan<char> key, out TValue value)
=> this.TryRemoveInternal(key, out value);
/// <summary>
/// Checks whether this dictionary contains the specified key.
/// </summary>
/// <param name="key">Key to check for in this dictionary.</param>
/// <returns>Whether the key was present in the dictionary.</returns>
public bool ContainsKey(string key)
=> this.ContainsKeyInternal(key.AsSpan());
/// <summary>
/// Checks whether this dictionary contains the specified key.
/// </summary>
/// <param name="key">Key to check for in this dictionary.</param>
/// <returns>Whether the key was present in the dictionary.</returns>
public bool ContainsKey(ReadOnlySpan<char> key)
=> this.ContainsKeyInternal(key);
/// <summary>
/// Removes all items from this dictionary.
/// </summary>
public void Clear()
{
this._internalBuckets.Clear();
this.Count = 0;
}
/// <summary>
/// Gets an enumerator over key-value pairs in this dictionary.
/// </summary>
/// <returns></returns>
public IEnumerator<KeyValuePair<string, TValue>> GetEnumerator()
=> new Enumerator(this);
/// <summary>
/// Removes the.
/// </summary>
/// <param name="key">The key.</param>
/// <returns>A bool.</returns>
bool IDictionary<string, TValue>.Remove(string key)
=> this.TryRemove(key.AsSpan(), out _);
/// <summary>
/// Adds the.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="value">The value.</param>
void IDictionary.Add(object key, object value)
{
if (!(key is string tkey))
throw new ArgumentException("Key needs to be an instance of a string.");
if (!(value is TValue tvalue))
{
tvalue = default;
if (tvalue != null)
throw new ArgumentException($"Value needs to be an instance of {typeof(TValue)}.");
}
this.Add(tkey, tvalue);
}
/// <summary>
/// Removes the.
/// </summary>
/// <param name="key">The key.</param>
void IDictionary.Remove(object key)
{
if (!(key is string tkey))
throw new ArgumentException("Key needs to be an instance of a string.");
this.TryRemove(tkey, out _);
}
/// <summary>
/// Contains the.
/// </summary>
/// <param name="key">The key.</param>
/// <returns>A bool.</returns>
bool IDictionary.Contains(object key)
{
if (!(key is string tkey))
throw new ArgumentException("Key needs to be an instance of a string.");
return this.ContainsKey(tkey);
}
/// <summary>
/// Gets the enumerator.
/// </summary>
/// <returns>An IDictionaryEnumerator.</returns>
IDictionaryEnumerator IDictionary.GetEnumerator()
=> new Enumerator(this);
/// <summary>
/// Adds the.
/// </summary>
/// <param name="item">The item.</param>
void ICollection<KeyValuePair<string, TValue>>.Add(KeyValuePair<string, TValue> item)
=> this.Add(item.Key, item.Value);
/// <summary>
/// Removes the.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>A bool.</returns>
bool ICollection<KeyValuePair<string, TValue>>.Remove(KeyValuePair<string, TValue> item)
=> this.TryRemove(item.Key, out _);
/// <summary>
/// Contains the.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>A bool.</returns>
bool ICollection<KeyValuePair<string, TValue>>.Contains(KeyValuePair<string, TValue> item)
=> this.TryGetValue(item.Key, out var value) && EqualityComparer<TValue>.Default.Equals(value, item.Value);
/// <summary>
/// Copies the to.
/// </summary>
/// <param name="array">The array.</param>
/// <param name="arrayIndex">The array index.</param>
void ICollection<KeyValuePair<string, TValue>>.CopyTo(KeyValuePair<string, TValue>[] array, int arrayIndex)
{
if (array.Length - arrayIndex < this.Count)
throw new ArgumentException("Target array is too small.", nameof(array));
var i = arrayIndex;
foreach (var (k, v) in this._internalBuckets)
{
var kdv = v;
while (kdv != null)
{
array[i++] = new KeyValuePair<string, TValue>(kdv.Key, kdv.Value);
kdv = kdv.Next;
}
}
}
/// <summary>
/// Copies the to.
/// </summary>
/// <param name="array">The array.</param>
/// <param name="arrayIndex">The array index.</param>
void ICollection.CopyTo(Array array, int arrayIndex)
{
if (array is KeyValuePair<string, TValue>[] tarray)
{
(this as ICollection<KeyValuePair<string, TValue>>).CopyTo(tarray, arrayIndex);
return;
}
if (array is not object[])
throw new ArgumentException($"Array needs to be an instance of {typeof(TValue[])} or object[].");
var i = arrayIndex;
foreach (var (k, v) in this._internalBuckets)
{
var kdv = v;
while (kdv != null)
{
array.SetValue(new KeyValuePair<string, TValue>(kdv.Key, kdv.Value), i++);
kdv = kdv.Next;
}
}
}
/// <summary>
/// Gets the enumerator.
/// </summary>
/// <returns>An IEnumerator.</returns>
IEnumerator IEnumerable.GetEnumerator()
=> this.GetEnumerator();
/// <summary>
/// Tries the insert internal.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="value">The value.</param>
/// <param name="replace">If true, replace.</param>
/// <returns>A bool.</returns>
private bool TryInsertInternal(string key, TValue value, bool replace)
{
if (key == null)
throw new ArgumentNullException(nameof(key), "Key cannot be null.");
var hash = key.CalculateKnuthHash();
if (!this._internalBuckets.ContainsKey(hash))
{
this._internalBuckets.Add(hash, new KeyedValue(key, hash, value));
this.Count++;
return true;
}
var kdv = this._internalBuckets[hash];
var kdvLast = kdv;
while (kdv != null)
{
if (kdv.Key == key)
{
if (!replace)
return false;
kdv.Value = value;
return true;
}
kdvLast = kdv;
kdv = kdv.Next;
}
kdvLast.Next = new KeyedValue(key, hash, value);
this.Count++;
return true;
}
/// <summary>
/// Tries the retrieve internal.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="value">The value.</param>
/// <returns>A bool.</returns>
private bool TryRetrieveInternal(ReadOnlySpan<char> key, out TValue value)
{
value = default;
var hash = key.CalculateKnuthHash();
if (!this._internalBuckets.TryGetValue(hash, out var kdv))
return false;
while (kdv != null)
{
if (key.SequenceEqual(kdv.Key.AsSpan()))
{
value = kdv.Value;
return true;
}
}
return false;
}
/// <summary>
/// Tries the remove internal.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="value">The value.</param>
/// <returns>A bool.</returns>
private bool TryRemoveInternal(ReadOnlySpan<char> key, out TValue value)
{
value = default;
var hash = key.CalculateKnuthHash();
if (!this._internalBuckets.TryGetValue(hash, out var kdv))
return false;
if (kdv.Next == null && key.SequenceEqual(kdv.Key.AsSpan()))
{
// Only bucket under this hash and key matches, pop the entire bucket
value = kdv.Value;
this._internalBuckets.Remove(hash);
this.Count--;
return true;
}
else if (kdv.Next == null)
{
// Only bucket under this hash and key does not match, cannot remove
return false;
}
else if (key.SequenceEqual(kdv.Key.AsSpan()))
{
// First key in the bucket matches, pop it and set its child as current bucket
value = kdv.Value;
this._internalBuckets[hash] = kdv.Next;
this.Count--;
return true;
}
var kdvLast = kdv;
kdv = kdv.Next;
while (kdv != null)
{
if (key.SequenceEqual(kdv.Key.AsSpan()))
{
// Key matched, remove this bucket from the chain
value = kdv.Value;
kdvLast.Next = kdv.Next;
this.Count--;
return true;
}
kdvLast = kdv;
kdv = kdv.Next;
}
return false;
}
/// <summary>
/// Contains the key internal.
/// </summary>
/// <param name="key">The key.</param>
/// <returns>A bool.</returns>
private bool ContainsKeyInternal(ReadOnlySpan<char> key)
{
var hash = key.CalculateKnuthHash();
if (!this._internalBuckets.TryGetValue(hash, out var kdv))
return false;
while (kdv != null)
{
if (key.SequenceEqual(kdv.Key.AsSpan()))
return true;
kdv = kdv.Next;
}
return false;
}
/// <summary>
/// Gets the keys internal.
/// </summary>
/// <returns>An ImmutableArray.</returns>
private ImmutableArray<string> GetKeysInternal()
{
var builder = ImmutableArray.CreateBuilder<string>(this.Count);
foreach (var value in this._internalBuckets.Values)
{
var kdv = value;
while (kdv != null)
{
builder.Add(kdv.Key);
kdv = kdv.Next;
}
}
return builder.MoveToImmutable();
}
/// <summary>
/// Gets the values internal.
/// </summary>
/// <returns>An ImmutableArray.</returns>
private ImmutableArray<TValue> GetValuesInternal()
{
var builder = ImmutableArray.CreateBuilder<TValue>(this.Count);
foreach (var value in this._internalBuckets.Values)
{
var kdv = value;
while (kdv != null)
{
builder.Add(kdv.Value);
kdv = kdv.Next;
}
}
return builder.MoveToImmutable();
}
/// <summary>
/// The keyed value.
/// </summary>
private class KeyedValue
{
/// <summary>
/// Gets the key hash.
/// </summary>
public ulong KeyHash { get; }
/// <summary>
/// Gets the key.
/// </summary>
public string Key { get; }
/// <summary>
/// Gets or sets the value.
/// </summary>
public TValue Value { get; set; }
/// <summary>
/// Gets or sets the next.
/// </summary>
public KeyedValue Next { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="KeyedValue"/> class.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="keyHash">The key hash.</param>
/// <param name="value">The value.</param>
public KeyedValue(string key, ulong keyHash, TValue value)
{
this.KeyHash = keyHash;
this.Key = key;
this.Value = value;
}
}
/// <summary>
/// The enumerator.
/// </summary>
private class Enumerator :
IEnumerator<KeyValuePair<string, TValue>>,
IDictionaryEnumerator
{
/// <summary>
/// Gets the current.
/// </summary>
public KeyValuePair<string, TValue> Current { get; private set; }
/// <summary>
/// Gets the current.
/// </summary>
object IEnumerator.Current => this.Current;
/// <summary>
/// Gets the key.
/// </summary>
object IDictionaryEnumerator.Key => this.Current.Key;
/// <summary>
/// Gets the value.
/// </summary>
object IDictionaryEnumerator.Value => this.Current.Value;
/// <summary>
/// Gets the entry.
/// </summary>
DictionaryEntry IDictionaryEnumerator.Entry => new(this.Current.Key, this.Current.Value);
/// <summary>
/// Gets the internal dictionary.
/// </summary>
private readonly CharSpanLookupDictionary<TValue> _internalDictionary;
/// <summary>
/// Gets the internal enumerator.
/// </summary>
private readonly IEnumerator<KeyValuePair<ulong, KeyedValue>> _internalEnumerator;
/// <summary>
/// Gets or sets the current value.
/// </summary>
private KeyedValue _currentValue;
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> class.
/// </summary>
/// <param name="spDict">The sp dict.</param>
public Enumerator(CharSpanLookupDictionary<TValue> spDict)
{
this._internalDictionary = spDict;
this._internalEnumerator = this._internalDictionary._internalBuckets.GetEnumerator();
}
/// <summary>
/// Moves the next.
/// </summary>
/// <returns>A bool.</returns>
public bool MoveNext()
{
var kdv = this._currentValue;
if (kdv == null)
{
if (!this._internalEnumerator.MoveNext())
return false;
kdv = this._internalEnumerator.Current.Value;
this.Current = new KeyValuePair<string, TValue>(kdv.Key, kdv.Value);
this._currentValue = kdv.Next;
return true;
}
this.Current = new KeyValuePair<string, TValue>(kdv.Key, kdv.Value);
this._currentValue = kdv.Next;
return true;
}
/// <summary>
/// Resets the.
/// </summary>
public void Reset()
{
this._internalEnumerator.Reset();
this.Current = default;
this._currentValue = null;
}
/// <summary>
/// Disposes the.
/// </summary>
public void Dispose() => this.Reset();
}
}
diff --git a/DisCatSharp.Common/Types/CharSpanLookupReadOnlyDictionary.cs b/DisCatSharp.Common/Types/CharSpanLookupReadOnlyDictionary.cs
index e10dd97ff..8d40f2dab 100644
--- a/DisCatSharp.Common/Types/CharSpanLookupReadOnlyDictionary.cs
+++ b/DisCatSharp.Common/Types/CharSpanLookupReadOnlyDictionary.cs
@@ -1,415 +1,415 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Collections.Immutable;
using System.Collections.ObjectModel;
namespace DisCatSharp.Common;
/// <summary>
/// Represents collection of string keys and <typeparamref name="TValue"/> values, allowing the use of <see cref="ReadOnlySpan{T}"/> for dictionary operations.
/// </summary>
/// <typeparam name="TValue">Type of items in this dictionary.</typeparam>
public sealed class CharSpanLookupReadOnlyDictionary<TValue> : IReadOnlyDictionary<string, TValue>
{
/// <summary>
/// Gets the collection of all keys present in this dictionary.
/// </summary>
public IEnumerable<string> Keys => this.GetKeysInternal();
/// <summary>
/// Gets the collection of all values present in this dictionary.
/// </summary>
public IEnumerable<TValue> Values => this.GetValuesInternal();
/// <summary>
/// Gets the total number of items in this dictionary.
/// </summary>
public int Count { get; }
/// <summary>
/// Gets a value corresponding to given key in this dictionary.
/// </summary>
/// <param name="key">Key to get or set the value for.</param>
/// <returns>Value matching the supplied key, if applicable.</returns>
public TValue this[string key]
{
get
{
if (key == null)
throw new ArgumentNullException(nameof(key));
if (!this.TryRetrieveInternal(key.AsSpan(), out var value))
throw new KeyNotFoundException($"The given key '{key}' was not present in the dictionary.");
return value;
}
}
/// <summary>
/// Gets a value corresponding to given key in this dictionary.
/// </summary>
/// <param name="key">Key to get or set the value for.</param>
/// <returns>Value matching the supplied key, if applicable.</returns>
public TValue this[ReadOnlySpan<char> key]
{
get
{
if (!this.TryRetrieveInternal(key, out var value))
throw new KeyNotFoundException($"The given key was not present in the dictionary.");
return value;
}
}
/// <summary>
/// Gets the internal buckets.
/// </summary>
private readonly IReadOnlyDictionary<ulong, KeyedValue> _internalBuckets;
/// <summary>
/// Creates a new <see cref="CharSpanLookupReadOnlyDictionary{TValue}"/> with string keys and items of type <typeparamref name="TValue"/> and populates it with key-value pairs from supplied dictionary.
/// </summary>
/// <param name="values">Dictionary containing items to populate this dictionary with.</param>
public CharSpanLookupReadOnlyDictionary(IDictionary<string, TValue> values)
: this(values as IEnumerable<KeyValuePair<string, TValue>>)
{ }
/// <summary>
/// Creates a new <see cref="CharSpanLookupReadOnlyDictionary{TValue}"/> with string keys and items of type <typeparamref name="TValue"/> and populates it with key-value pairs from supplied dictionary.
/// </summary>
/// <param name="values">Dictionary containing items to populate this dictionary with.</param>
public CharSpanLookupReadOnlyDictionary(IReadOnlyDictionary<string, TValue> values)
: this(values as IEnumerable<KeyValuePair<string, TValue>>)
{ }
/// <summary>
/// Creates a new <see cref="CharSpanLookupReadOnlyDictionary{TValue}"/> with string keys and items of type <typeparamref name="TValue"/> and populates it with key-value pairs from supplied key-value collection.
/// </summary>
/// <param name="values">Dictionary containing items to populate this dictionary with.</param>
public CharSpanLookupReadOnlyDictionary(IEnumerable<KeyValuePair<string, TValue>> values)
{
this._internalBuckets = PrepareItems(values, out var count);
this.Count = count;
}
/// <summary>
/// Attempts to retrieve a value corresponding to the supplied key from this dictionary.
/// </summary>
/// <param name="key">Key to retrieve the value for.</param>
/// <param name="value">Retrieved value.</param>
/// <returns>Whether the operation was successful.</returns>
public bool TryGetValue(string key, out TValue value)
{
if (key == null)
throw new ArgumentNullException(nameof(key));
return this.TryRetrieveInternal(key.AsSpan(), out value);
}
/// <summary>
/// Attempts to retrieve a value corresponding to the supplied key from this dictionary.
/// </summary>
/// <param name="key">Key to retrieve the value for.</param>
/// <param name="value">Retrieved value.</param>
/// <returns>Whether the operation was successful.</returns>
public bool TryGetValue(ReadOnlySpan<char> key, out TValue value)
=> this.TryRetrieveInternal(key, out value);
/// <summary>
/// Checks whether this dictionary contains the specified key.
/// </summary>
/// <param name="key">Key to check for in this dictionary.</param>
/// <returns>Whether the key was present in the dictionary.</returns>
public bool ContainsKey(string key)
=> this.ContainsKeyInternal(key.AsSpan());
/// <summary>
/// Checks whether this dictionary contains the specified key.
/// </summary>
/// <param name="key">Key to check for in this dictionary.</param>
/// <returns>Whether the key was present in the dictionary.</returns>
public bool ContainsKey(ReadOnlySpan<char> key)
=> this.ContainsKeyInternal(key);
/// <summary>
/// Gets an enumerator over key-value pairs in this dictionary.
/// </summary>
/// <returns></returns>
public IEnumerator<KeyValuePair<string, TValue>> GetEnumerator()
=> new Enumerator(this);
/// <summary>
/// Gets the enumerator.
/// </summary>
/// <returns>An IEnumerator.</returns>
IEnumerator IEnumerable.GetEnumerator()
=> this.GetEnumerator();
/// <summary>
/// Tries the retrieve internal.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="value">The value.</param>
/// <returns>A bool.</returns>
private bool TryRetrieveInternal(ReadOnlySpan<char> key, out TValue value)
{
value = default;
var hash = key.CalculateKnuthHash();
if (!this._internalBuckets.TryGetValue(hash, out var kdv))
return false;
while (kdv != null)
{
if (key.SequenceEqual(kdv.Key.AsSpan()))
{
value = kdv.Value;
return true;
}
}
return false;
}
/// <summary>
/// Contains the key internal.
/// </summary>
/// <param name="key">The key.</param>
/// <returns>A bool.</returns>
private bool ContainsKeyInternal(ReadOnlySpan<char> key)
{
var hash = key.CalculateKnuthHash();
if (!this._internalBuckets.TryGetValue(hash, out var kdv))
return false;
while (kdv != null)
{
if (key.SequenceEqual(kdv.Key.AsSpan()))
return true;
kdv = kdv.Next;
}
return false;
}
/// <summary>
/// Gets the keys internal.
/// </summary>
/// <returns>An ImmutableArray.</returns>
private ImmutableArray<string> GetKeysInternal()
{
var builder = ImmutableArray.CreateBuilder<string>(this.Count);
foreach (var value in this._internalBuckets.Values)
{
var kdv = value;
while (kdv != null)
{
builder.Add(kdv.Key);
kdv = kdv.Next;
}
}
return builder.MoveToImmutable();
}
/// <summary>
/// Gets the values internal.
/// </summary>
/// <returns>An ImmutableArray.</returns>
private ImmutableArray<TValue> GetValuesInternal()
{
var builder = ImmutableArray.CreateBuilder<TValue>(this.Count);
foreach (var value in this._internalBuckets.Values)
{
var kdv = value;
while (kdv != null)
{
builder.Add(kdv.Value);
kdv = kdv.Next;
}
}
return builder.MoveToImmutable();
}
/// <summary>
/// Prepares the items.
/// </summary>
/// <param name="items">The items.</param>
/// <param name="count">The count.</param>
/// <returns>An IReadOnlyDictionary.</returns>
private static IReadOnlyDictionary<ulong, KeyedValue> PrepareItems(IEnumerable<KeyValuePair<string, TValue>> items, out int count)
{
count = 0;
var dict = new Dictionary<ulong, KeyedValue>();
foreach (var (k, v) in items)
{
if (k == null)
throw new ArgumentException("Keys cannot be null.", nameof(items));
var hash = k.CalculateKnuthHash();
if (!dict.ContainsKey(hash))
{
dict.Add(hash, new KeyedValue(k, hash, v));
count++;
continue;
}
var kdv = dict[hash];
var kdvLast = kdv;
while (kdv != null)
{
if (kdv.Key == k)
throw new ArgumentException("Given key is already present in the dictionary.", nameof(items));
kdvLast = kdv;
kdv = kdv.Next;
}
kdvLast.Next = new KeyedValue(k, hash, v);
count++;
}
return new ReadOnlyDictionary<ulong, KeyedValue>(dict);
}
/// <summary>
/// The keyed value.
/// </summary>
private class KeyedValue
{
/// <summary>
/// Gets the key hash.
/// </summary>
public ulong KeyHash { get; }
/// <summary>
/// Gets the key.
/// </summary>
public string Key { get; }
/// <summary>
/// Gets or sets the value.
/// </summary>
public TValue Value { get; set; }
/// <summary>
/// Gets or sets the next.
/// </summary>
public KeyedValue Next { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="KeyedValue"/> class.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="keyHash">The key hash.</param>
/// <param name="value">The value.</param>
public KeyedValue(string key, ulong keyHash, TValue value)
{
this.KeyHash = keyHash;
this.Key = key;
this.Value = value;
}
}
/// <summary>
/// The enumerator.
/// </summary>
private class Enumerator : IEnumerator<KeyValuePair<string, TValue>>
{
/// <summary>
/// Gets the current.
/// </summary>
public KeyValuePair<string, TValue> Current { get; private set; }
/// <summary>
/// Gets the current.
/// </summary>
object IEnumerator.Current => this.Current;
/// <summary>
/// Gets the internal dictionary.
/// </summary>
private readonly CharSpanLookupReadOnlyDictionary<TValue> _internalDictionary;
/// <summary>
/// Gets the internal enumerator.
/// </summary>
private readonly IEnumerator<KeyValuePair<ulong, KeyedValue>> _internalEnumerator;
/// <summary>
/// Gets or sets the current value.
/// </summary>
private KeyedValue _currentValue;
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> class.
/// </summary>
/// <param name="spDict">The sp dict.</param>
public Enumerator(CharSpanLookupReadOnlyDictionary<TValue> spDict)
{
this._internalDictionary = spDict;
this._internalEnumerator = this._internalDictionary._internalBuckets.GetEnumerator();
}
/// <summary>
/// Moves the next.
/// </summary>
/// <returns>A bool.</returns>
public bool MoveNext()
{
var kdv = this._currentValue;
if (kdv == null)
{
if (!this._internalEnumerator.MoveNext())
return false;
kdv = this._internalEnumerator.Current.Value;
this.Current = new KeyValuePair<string, TValue>(kdv.Key, kdv.Value);
this._currentValue = kdv.Next;
return true;
}
this.Current = new KeyValuePair<string, TValue>(kdv.Key, kdv.Value);
this._currentValue = kdv.Next;
return true;
}
/// <summary>
/// Resets the.
/// </summary>
public void Reset()
{
this._internalEnumerator.Reset();
this.Current = default;
this._currentValue = null;
}
/// <summary>
/// Disposes the.
/// </summary>
public void Dispose() => this.Reset();
}
}
diff --git a/DisCatSharp.Common/Types/ContinuousMemoryBuffer.cs b/DisCatSharp.Common/Types/ContinuousMemoryBuffer.cs
index 95937b8fb..d48b0b3cc 100644
--- a/DisCatSharp.Common/Types/ContinuousMemoryBuffer.cs
+++ b/DisCatSharp.Common/Types/ContinuousMemoryBuffer.cs
@@ -1,254 +1,254 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace DisCatSharp.Common.Types;
/// <summary>
/// Provides a resizable memory buffer analogous to <see cref="MemoryBuffer{T}"/>, using a single continuous memory region instead.
/// </summary>
/// <typeparam name="T">Type of item to hold in the buffer.</typeparam>
public sealed class ContinuousMemoryBuffer<T> : IMemoryBuffer<T> where T : unmanaged
{
/// <inheritdoc />
public ulong Capacity => (ulong)this._buff.Length;
/// <inheritdoc />
public ulong Length => (ulong)this._pos;
/// <inheritdoc />
public ulong Count => (ulong)(this._pos / this._itemSize);
private readonly MemoryPool<byte> _pool;
private IMemoryOwner<byte> _buffOwner;
private Memory<byte> _buff;
private readonly bool _clear;
private int _pos;
private readonly int _itemSize;
private bool _isDisposed;
/// <summary>
/// Creates a new buffer with a specified segment size, specified number of initially-allocated segments, and supplied memory pool.
/// </summary>
/// <param name="initialSize">Initial size of the buffer in bytes. Defaults to 64KiB.</param>
/// <param name="memPool">Memory pool to use for renting buffers. Defaults to <see cref="MemoryPool{T}.Shared"/>.</param>
/// <param name="clearOnDispose">Determines whether the underlying buffers should be cleared on exit. If dealing with sensitive data, it might be a good idea to set this option to true.</param>
public ContinuousMemoryBuffer(int initialSize = 65536, MemoryPool<byte> memPool = default, bool clearOnDispose = false)
{
this._itemSize = Unsafe.SizeOf<T>();
this._pool = memPool ?? MemoryPool<byte>.Shared;
this._clear = clearOnDispose;
this._buffOwner = this._pool.Rent(initialSize);
this._buff = this._buffOwner.Memory;
this._isDisposed = false;
}
/// <inheritdoc />
public void Write(ReadOnlySpan<T> data)
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
var bytes = MemoryMarshal.AsBytes(data);
this.EnsureSize(this._pos + bytes.Length);
bytes.CopyTo(this._buff[this._pos..].Span);
this._pos += bytes.Length;
}
/// <inheritdoc />
public void Write(T[] data, int start, int count)
=> this.Write(data.AsSpan(start, count));
/// <inheritdoc />
public void Write(ArraySegment<T> data)
=> this.Write(data.AsSpan());
/// <inheritdoc />
public void Write(Stream stream)
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
if (stream.CanSeek)
this.WriteStreamSeekable(stream);
else
this.WriteStreamUnseekable(stream);
}
/// <summary>
/// Writes the stream seekable.
/// </summary>
/// <param name="stream">The stream.</param>
private void WriteStreamSeekable(Stream stream)
{
if (stream.Length > int.MaxValue)
throw new ArgumentException("Stream is too long.", nameof(stream));
this.EnsureSize(this._pos + (int)stream.Length);
var memo = ArrayPool<byte>.Shared.Rent((int)stream.Length);
try
{
var br = stream.Read(memo, 0, memo.Length);
memo.AsSpan(0, br).CopyTo(this._buff[this._pos..].Span);
}
finally
{
ArrayPool<byte>.Shared.Return(memo);
}
this._pos += (int)stream.Length;
}
/// <summary>
/// Writes the stream unseekable.
/// </summary>
/// <param name="stream">The stream.</param>
private void WriteStreamUnseekable(Stream stream)
{
var memo = ArrayPool<byte>.Shared.Rent(4096);
try
{
var br = 0;
while ((br = stream.Read(memo, 0, memo.Length)) != 0)
{
this.EnsureSize(this._pos + br);
memo.AsSpan(0, br).CopyTo(this._buff[this._pos..].Span);
this._pos += br;
}
}
finally
{
ArrayPool<byte>.Shared.Return(memo);
}
}
/// <inheritdoc />
public bool Read(Span<T> destination, ulong source, out int itemsWritten)
{
itemsWritten = 0;
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
source *= (ulong)this._itemSize;
if (source > this.Count)
throw new ArgumentOutOfRangeException(nameof(source), "Cannot copy data from beyond the buffer.");
var start = (int)source;
var sbuff = this._buff[start..this._pos ].Span;
var dbuff = MemoryMarshal.AsBytes(destination);
if (sbuff.Length > dbuff.Length)
sbuff = sbuff[..dbuff.Length];
itemsWritten = sbuff.Length / this._itemSize;
sbuff.CopyTo(dbuff);
return this.Length - source != (ulong)itemsWritten;
}
/// <inheritdoc />
public bool Read(T[] data, int start, int count, ulong source, out int itemsWritten)
=> this.Read(data.AsSpan(start, count), source, out itemsWritten);
/// <inheritdoc />
public bool Read(ArraySegment<T> data, ulong source, out int itemsWritten)
=> this.Read(data.AsSpan(), source, out itemsWritten);
/// <inheritdoc />
public T[] ToArray()
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
return MemoryMarshal.Cast<byte, T>(this._buff[..this._pos].Span).ToArray();
}
/// <inheritdoc />
public void CopyTo(Stream destination)
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
var buff = this._buff[..this._pos].ToArray();
destination.Write(buff, 0, buff.Length);
}
/// <inheritdoc />
public void Clear()
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
this._pos = 0;
}
/// <summary>
/// Disposes of any resources claimed by this buffer.
/// </summary>
public void Dispose()
{
if (this._isDisposed)
return;
this._isDisposed = true;
if (this._clear)
this._buff.Span.Clear();
this._buffOwner.Dispose();
this._buff = default;
}
/// <summary>
/// Ensures the size.
/// </summary>
/// <param name="newCapacity">The new capacity.</param>
private void EnsureSize(int newCapacity)
{
var cap = this._buff.Length;
if (cap >= newCapacity)
return;
var factor = newCapacity / cap;
if (newCapacity % cap != 0)
++factor;
var newActualCapacity = cap * factor;
var newBuffOwner = this._pool.Rent(newActualCapacity);
var newBuff = newBuffOwner.Memory;
this._buff.Span.CopyTo(newBuff.Span);
if (this._clear)
this._buff.Span.Clear();
this._buffOwner.Dispose();
this._buffOwner = newBuffOwner;
this._buff = newBuff;
}
}
diff --git a/DisCatSharp.Common/Types/IMemoryBuffer.cs b/DisCatSharp.Common/Types/IMemoryBuffer.cs
index 7896a4bd5..8b616df39 100644
--- a/DisCatSharp.Common/Types/IMemoryBuffer.cs
+++ b/DisCatSharp.Common/Types/IMemoryBuffer.cs
@@ -1,126 +1,126 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
namespace DisCatSharp.Common.Types;
/// <summary>
/// An interface describing the API of resizable memory buffers, such as <see cref="MemoryBuffer{T}"/> and <see cref="ContinuousMemoryBuffer{T}"/>.
/// </summary>
/// <typeparam name="T">Type of item to hold in the buffer.</typeparam>
public interface IMemoryBuffer<T> : IDisposable where T : unmanaged
{
/// <summary>
/// Gets the total capacity of this buffer. The capacity is the number of segments allocated, multiplied by size of individual segment.
/// </summary>
ulong Capacity { get; }
/// <summary>
/// Gets the amount of bytes currently written to the buffer. This number is never greater than <see cref="Capacity"/>.
/// </summary>
ulong Length { get; }
/// <summary>
/// Gets the number of items currently written to the buffer. This number is equal to <see cref="Count"/> divided by size of <typeparamref name="T"/>.
/// </summary>
ulong Count { get; }
/// <summary>
/// Appends data from a supplied buffer to this buffer, growing it if necessary.
/// </summary>
/// <param name="data">Buffer containing data to write.</param>
void Write(ReadOnlySpan<T> data);
/// <summary>
/// Appends data from a supplied array to this buffer, growing it if necessary.
/// </summary>
/// <param name="data">Array containing data to write.</param>
/// <param name="start">Index from which to start reading the data.</param>
/// <param name="count">Number of bytes to read from the source.</param>
void Write(T[] data, int start, int count);
/// <summary>
/// Appends data from a supplied array slice to this buffer, growing it if necessary.
/// </summary>
/// <param name="data">Array slice containing data to write.</param>
void Write(ArraySegment<T> data);
/// <summary>
/// Appends data from a supplied stream to this buffer, growing it if necessary.
/// </summary>
/// <param name="stream">Stream to copy data from.</param>
void Write(Stream stream);
/// <summary>
/// Reads data from this buffer to the specified destination buffer. This method will write either as many
/// bytes as there are in the destination buffer, or however many bytes are available in this buffer,
/// whichever is less.
/// </summary>
/// <param name="destination">Buffer to read the data from this buffer into.</param>
/// <param name="source">Starting position in this buffer to read from.</param>
/// <param name="itemsWritten">Number of items written to the destination buffer.</param>
/// <returns>Whether more data is available in this buffer.</returns>
bool Read(Span<T> destination, ulong source, out int itemsWritten);
/// <summary>
/// Reads data from this buffer to specified destination array. This method will write either as many bytes
/// as specified for the destination array, or however many bytes are available in this buffer, whichever is
/// less.
/// </summary>
/// <param name="data">Array to read the data from this buffer into.</param>
/// <param name="start">Starting position in the target array to write to.</param>
/// <param name="count">Maximum number of bytes to write to target array.</param>
/// <param name="source">Starting position in this buffer to read from.</param>
/// <param name="itemsWritten">Number of items written to the destination buffer.</param>
/// <returns>Whether more data is available in this buffer.</returns>
bool Read(T[] data, int start, int count, ulong source, out int itemsWritten);
/// <summary>
/// Reads data from this buffer to specified destination array slice. This method will write either as many
/// bytes as specified in the target slice, or however many bytes are available in this buffer, whichever is
/// less.
/// </summary>
/// <param name="data"></param>
/// <param name="source"></param>
/// <param name="itemsWritten">Number of items written to the destination buffer.</param>
/// <returns>Whether more data is available in this buffer.</returns>
bool Read(ArraySegment<T> data, ulong source, out int itemsWritten);
/// <summary>
/// Converts this buffer into a single continuous byte array.
/// </summary>
/// <returns>Converted byte array.</returns>
T[] ToArray();
/// <summary>
/// Copies all the data from this buffer to a stream.
/// </summary>
/// <param name="destination">Stream to copy this buffer's data to.</param>
void CopyTo(Stream destination);
/// <summary>
/// Resets the buffer's pointer to the beginning, allowing for reuse.
/// </summary>
void Clear();
}
diff --git a/DisCatSharp.Common/Types/LinqMethods.cs b/DisCatSharp.Common/Types/LinqMethods.cs
index 7a4c24407..8c72f4ef9 100644
--- a/DisCatSharp.Common/Types/LinqMethods.cs
+++ b/DisCatSharp.Common/Types/LinqMethods.cs
@@ -1,75 +1,75 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
namespace DisCatSharp.Common;
/// <summary>
/// Various Methods for Linq
/// </summary>
public static class LinqMethods
{
/// <summary>
/// Safely tries to get the first match out of a list.
/// </summary>
/// <typeparam name="TSource">Value type of list.</typeparam>
/// <param name="list">The list to use.</param>
/// <param name="predicate">The predicate.</param>
/// <param name="value">The value to get if succeeded</param>
/// <returns>Whether a value was found.</returns>
public static bool GetFirstValueWhere<TSource>(this List<TSource?> list, Func<TSource?, bool> predicate, out TSource? value)
{
if (list.EmptyOrNull())
{
value = default;
return false;
}
value = list.Where(predicate).FirstOrDefault();
return value is not null;
}
/// <summary>
/// Safely tries to extract the value of the first match where target key is found, otherwise null.
/// </summary>
/// <typeparam name="TKey">Key type of dictionary.</typeparam>
/// <typeparam name="TValue">Value type of dictionary.</typeparam>
/// <param name="dict">The dictionary to use.</param>
/// <param name="key">The key to search for.</param>
/// <param name="value">The value to get if succeeded.</param>
/// <returns>Whether a value was found through the key.</returns>
public static bool GetFirstValueByKey<TKey, TValue>(this Dictionary<TKey, TValue?>? dict, TKey key, out TValue? value)
where TKey : notnull
{
if (dict == null)
{
value = default;
return false;
}
return dict.TryGetValue(key, out value);
}
}
diff --git a/DisCatSharp.Common/Types/MemoryBuffer.cs b/DisCatSharp.Common/Types/MemoryBuffer.cs
index fd91cdd1f..279a8531e 100644
--- a/DisCatSharp.Common/Types/MemoryBuffer.cs
+++ b/DisCatSharp.Common/Types/MemoryBuffer.cs
@@ -1,339 +1,339 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace DisCatSharp.Common.Types;
/// <summary>
/// Provides a resizable memory buffer, which can be read from and written to. It will automatically resize whenever required.
/// </summary>
/// <typeparam name="T">Type of item to hold in the buffer.</typeparam>
public sealed class MemoryBuffer<T> : IMemoryBuffer<T> where T : unmanaged
{
/// <inheritdoc />
public ulong Capacity => this._segments.Aggregate(0UL, (a, x) => a + (ulong)x.Memory.Length); // .Sum() does only int
/// <inheritdoc />
public ulong Length { get; private set; }
/// <inheritdoc />
public ulong Count => this.Length / (ulong)this._itemSize;
private readonly MemoryPool<byte> _pool;
private readonly int _segmentSize;
private int _lastSegmentLength;
private int _segNo;
private readonly bool _clear;
private readonly List<IMemoryOwner<byte>> _segments;
private readonly int _itemSize;
private bool _isDisposed;
/// <summary>
/// Creates a new buffer with a specified segment size, specified number of initially-allocated segments, and supplied memory pool.
/// </summary>
/// <param name="segmentSize">Byte size of an individual segment. Defaults to 64KiB.</param>
/// <param name="initialSegmentCount">Number of segments to allocate. Defaults to 0.</param>
/// <param name="memPool">Memory pool to use for renting buffers. Defaults to <see cref="MemoryPool{T}.Shared"/>.</param>
/// <param name="clearOnDispose">Determines whether the underlying buffers should be cleared on exit. If dealing with sensitive data, it might be a good idea to set this option to true.</param>
public MemoryBuffer(int segmentSize = 65536, int initialSegmentCount = 0, MemoryPool<byte> memPool = default, bool clearOnDispose = false)
{
this._itemSize = Unsafe.SizeOf<T>();
if (segmentSize % this._itemSize != 0)
throw new ArgumentException("Segment size must match size of individual item.");
this._pool = memPool ?? MemoryPool<byte>.Shared;
this._segmentSize = segmentSize;
this._segNo = 0;
this._lastSegmentLength = 0;
this._clear = clearOnDispose;
this._segments = Enumerable.Range(0, initialSegmentCount)
.Select(x => this._pool.Rent(this._segmentSize))
.ToList();
this.Length = 0;
this._isDisposed = false;
}
/// <inheritdoc />
public void Write(ReadOnlySpan<T> data)
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
var src = MemoryMarshal.AsBytes(data);
this.Grow(src.Length);
while (this._segNo < this._segments.Count && src.Length > 0)
{
var seg = this._segments[this._segNo];
var mem = seg.Memory;
var avs = mem.Length - this._lastSegmentLength;
avs = avs > src.Length
? src.Length
: avs;
var dmem = mem[this._lastSegmentLength..];
src[..avs].CopyTo(dmem.Span);
src = src[avs..];
this.Length += (ulong)avs;
this._lastSegmentLength += avs;
if (this._lastSegmentLength == mem.Length)
{
this._segNo++;
this._lastSegmentLength = 0;
}
}
}
/// <inheritdoc />
public void Write(T[] data, int start, int count)
=> this.Write(data.AsSpan(start, count));
/// <inheritdoc />
public void Write(ArraySegment<T> data)
=> this.Write(data.AsSpan());
/// <inheritdoc />
public void Write(Stream stream)
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
if (stream.CanSeek)
this.WriteStreamSeekable(stream);
else
this.WriteStreamUnseekable(stream);
}
/// <summary>
/// Writes the stream seekable.
/// </summary>
/// <param name="stream">The stream.</param>
private void WriteStreamSeekable(Stream stream)
{
var len = (int)(stream.Length - stream.Position);
this.Grow(len);
var buff = new byte[this._segmentSize];
while (this._segNo < this._segments.Count && len > 0)
{
var seg = this._segments[this._segNo];
var mem = seg.Memory;
var avs = mem.Length - this._lastSegmentLength;
avs = avs > len
? len
: avs;
var dmem = mem[this._lastSegmentLength..];
var lsl = this._lastSegmentLength;
var slen = dmem.Span.Length - lsl;
stream.Read(buff, 0, slen);
buff.AsSpan(0, slen).CopyTo(dmem.Span);
len -= dmem.Span.Length;
this.Length += (ulong)avs;
this._lastSegmentLength += avs;
if (this._lastSegmentLength == mem.Length)
{
this._segNo++;
this._lastSegmentLength = 0;
}
}
}
/// <summary>
/// Writes the stream unseekable.
/// </summary>
/// <param name="stream">The stream.</param>
private void WriteStreamUnseekable(Stream stream)
{
var read = 0;
var buff = new byte[this._segmentSize];
var buffs = buff.AsSpan();
while ((read = stream.Read(buff, 0, buff.Length - this._lastSegmentLength)) != 0)
this.Write(MemoryMarshal.Cast<byte, T>(buffs[..read]));
}
/// <inheritdoc />
public bool Read(Span<T> destination, ulong source, out int itemsWritten)
{
itemsWritten = 0;
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
source *= (ulong)this._itemSize;
if (source > this.Count)
throw new ArgumentOutOfRangeException(nameof(source), "Cannot copy data from beyond the buffer.");
// Find where to begin
var i = 0;
for (; i < this._segments.Count; i++)
{
var seg = this._segments[i];
var mem = seg.Memory;
if ((ulong)mem.Length > source)
break;
source -= (ulong)mem.Length;
}
// Do actual copy
var dl = (int)(this.Length - source);
var sri = (int)source;
var dst = MemoryMarshal.AsBytes(destination);
for (; i < this._segments.Count && dst.Length > 0; i++)
{
var seg = this._segments[i];
var mem = seg.Memory;
var src = mem.Span;
if (sri != 0)
{
src = src[sri..];
sri = 0;
}
if (itemsWritten + src.Length > dl)
src = src[..(dl - itemsWritten)];
if (src.Length > dst.Length)
src = src[..dst.Length];
src.CopyTo(dst);
dst = dst[src.Length..];
itemsWritten += src.Length;
}
itemsWritten /= this._itemSize;
return this.Length - source != (ulong)itemsWritten;
}
/// <inheritdoc />
public bool Read(T[] data, int start, int count, ulong source, out int itemsWritten)
=> this.Read(data.AsSpan(start, count), source, out itemsWritten);
/// <inheritdoc />
public bool Read(ArraySegment<T> data, ulong source, out int itemsWritten)
=> this.Read(data.AsSpan(), source, out itemsWritten);
/// <inheritdoc />
public T[] ToArray()
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
var bytes = new T[this.Count];
this.Read(bytes, 0, out _);
return bytes;
}
/// <inheritdoc />
public void CopyTo(Stream destination)
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
var longest = this._segments.Max(x => x.Memory.Length);
var buff = new byte[longest];
foreach (var seg in this._segments)
{
var mem = seg.Memory.Span;
var spn = buff.AsSpan(0, mem.Length);
mem.CopyTo(spn);
destination.Write(buff, 0, spn.Length);
}
}
/// <inheritdoc />
public void Clear()
{
if (this._isDisposed)
throw new ObjectDisposedException("This buffer is disposed.");
this._segNo = 0;
this._lastSegmentLength = 0;
this.Length = 0;
}
/// <summary>
/// Disposes of any resources claimed by this buffer.
/// </summary>
public void Dispose()
{
if (this._isDisposed)
return;
this._isDisposed = true;
foreach (var segment in this._segments)
{
if (this._clear)
segment.Memory.Span.Clear();
segment.Dispose();
}
}
/// <summary>
/// Grows the.
/// </summary>
/// <param name="minAmount">The min amount.</param>
private void Grow(int minAmount)
{
var capacity = this.Capacity;
var length = this.Length;
var totalAmt = length + (ulong)minAmount;
if (capacity >= totalAmt)
return; // we're good
var amt = (int)(totalAmt - capacity);
var segCount = amt / this._segmentSize;
if (amt % this._segmentSize != 0)
segCount++;
// Basically List<T>.EnsureCapacity
// Default grow behaviour is minimum current*2
var segCap = this._segments.Count + segCount;
if (segCap > this._segments.Capacity)
this._segments.Capacity = segCap < this._segments.Capacity * 2
? this._segments.Capacity * 2
: segCap;
for (var i = 0; i < segCount; i++)
this._segments.Add(this._pool.Rent(this._segmentSize));
}
}
diff --git a/DisCatSharp.Common/Types/SecureRandom.cs b/DisCatSharp.Common/Types/SecureRandom.cs
index c7a800252..376639676 100644
--- a/DisCatSharp.Common/Types/SecureRandom.cs
+++ b/DisCatSharp.Common/Types/SecureRandom.cs
@@ -1,331 +1,331 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Provides a cryptographically-secure pseudorandom number generator (CSPRNG) implementation compatible with <see cref="Random"/>.
/// </summary>
public sealed class SecureRandom : Random, IDisposable
{
/// <summary>
/// Gets the r n g.
/// </summary>
private readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create();
private volatile bool _isDisposed;
/// <summary>
/// Creates a new instance of <see cref="SecureRandom"/>.
/// </summary>
public SecureRandom()
{ }
/// <summary>
/// Finalizes this <see cref="SecureRandom"/> instance by disposing it.
/// </summary>
~SecureRandom()
{
this.Dispose();
}
/// <summary>
/// Fills a supplied buffer with random bytes.
/// </summary>
/// <param name="buffer">Buffer to fill with random bytes.</param>
public void GetBytes(byte[] buffer) => this._rng.GetBytes(buffer);
/// <summary>
/// Fills a supplied buffer with random nonzero bytes.
/// </summary>
/// <param name="buffer">Buffer to fill with random nonzero bytes.</param>
public void GetNonZeroBytes(byte[] buffer) => this._rng.GetNonZeroBytes(buffer);
/// <summary>
/// Fills a supplied memory region with random bytes.
/// </summary>
/// <param name="buffer">Memory region to fill with random bytes.</param>
public void GetBytes(Span<byte> buffer)
{
var buff = ArrayPool<byte>.Shared.Rent(buffer.Length);
try
{
var buffSpan = buff.AsSpan(0, buffer.Length);
this._rng.GetBytes(buff);
buffSpan.CopyTo(buffer);
}
finally
{
ArrayPool<byte>.Shared.Return(buff);
}
}
/// <summary>
/// Fills a supplied memory region with random nonzero bytes.
/// </summary>
/// <param name="buffer">Memory region to fill with random nonzero bytes.</param>
public void GetNonZeroBytes(Span<byte> buffer)
{
var buff = ArrayPool<byte>.Shared.Rent(buffer.Length);
try
{
var buffSpan = buff.AsSpan(0, buffer.Length);
this._rng.GetNonZeroBytes(buff);
buffSpan.CopyTo(buffer);
}
finally
{
ArrayPool<byte>.Shared.Return(buff);
}
}
/// <summary>
/// Generates a signed 8-bit integer within specified range.
/// </summary>
/// <param name="min">Minimum value to generate. Defaults to 0.</param>
/// <param name="max">Maximum value to generate. Defaults to <see cref="sbyte.MaxValue"/>.</param>
/// <returns>Generated random value.</returns>
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<sbyte>()) % (max - min) + min - offset);
}
/// <summary>
/// Generates a unsigned 8-bit integer within specified range.
/// </summary>
/// <param name="min">Minimum value to generate. Defaults to 0.</param>
/// <param name="max">Maximum value to generate. Defaults to <see cref="byte.MaxValue"/>.</param>
/// <returns>Generated random value.</returns>
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<byte>() % (max - min) + min);
}
/// <summary>
/// Generates a signed 16-bit integer within specified range.
/// </summary>
/// <param name="min">Minimum value to generate. Defaults to 0.</param>
/// <param name="max">Maximum value to generate. Defaults to <see cref="short.MaxValue"/>.</param>
/// <returns>Generated random value.</returns>
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<short>()) % (max - min) + min - offset);
}
/// <summary>
/// Generates a unsigned 16-bit integer within specified range.
/// </summary>
/// <param name="min">Minimum value to generate. Defaults to 0.</param>
/// <param name="max">Maximum value to generate. Defaults to <see cref="ushort.MaxValue"/>.</param>
/// <returns>Generated random value.</returns>
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<ushort>() % (max - min) + min);
}
/// <summary>
/// Generates a signed 32-bit integer within specified range.
/// </summary>
/// <param name="min">Minimum value to generate. Defaults to 0.</param>
/// <param name="max">Maximum value to generate. Defaults to <see cref="int.MaxValue"/>.</param>
/// <returns>Generated random value.</returns>
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<int>()) % (max - min) + min - offset;
}
/// <summary>
/// Generates a unsigned 32-bit integer within specified range.
/// </summary>
/// <param name="min">Minimum value to generate. Defaults to 0.</param>
/// <param name="max">Maximum value to generate. Defaults to <see cref="uint.MaxValue"/>.</param>
/// <returns>Generated random value.</returns>
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<uint>() % (max - min) + min;
}
/// <summary>
/// Generates a signed 64-bit integer within specified range.
/// </summary>
/// <param name="min">Minimum value to generate. Defaults to 0.</param>
/// <param name="max">Maximum value to generate. Defaults to <see cref="long.MaxValue"/>.</param>
/// <returns>Generated random value.</returns>
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<long>()) % (max - min) + min - offset;
}
/// <summary>
/// Generates a unsigned 64-bit integer within specified range.
/// </summary>
/// <param name="min">Minimum value to generate. Defaults to 0.</param>
/// <param name="max">Maximum value to generate. Defaults to <see cref="ulong.MaxValue"/>.</param>
/// <returns>Generated random value.</returns>
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<ulong>() % (max - min) + min;
}
/// <summary>
/// Generates a 32-bit floating-point number between 0.0 and 1.0.
/// </summary>
/// <returns>Generated 32-bit floating-point number.</returns>
public float GetSingle()
{
var (i1, i2) = ((float)this.GetInt32(), (float)this.GetInt32());
return i1 / i2 % 1.0F;
}
/// <summary>
/// Generates a 64-bit floating-point number between 0.0 and 1.0.
/// </summary>
/// <returns>Generated 64-bit floating-point number.</returns>
public double GetDouble()
{
var (i1, i2) = ((double)this.GetInt64(), (double)this.GetInt64());
return i1 / i2 % 1.0;
}
/// <summary>
/// Generates a 32-bit integer between 0 and <see cref="int.MaxValue"/>. Upper end exclusive.
/// </summary>
/// <returns>Generated 32-bit integer.</returns>
public override int Next()
=> this.GetInt32();
/// <summary>
/// Generates a 32-bit integer between 0 and <paramref name="maxValue"/>. Upper end exclusive.
/// </summary>
/// <param name="maxValue">Maximum value of the generated integer.</param>
/// <returns>Generated 32-bit integer.</returns>
public override int Next(int maxValue)
=> this.GetInt32(0, maxValue);
/// <summary>
/// Generates a 32-bit integer between <paramref name="minValue"/> and <paramref name="maxValue"/>. Upper end exclusive.
/// </summary>
/// <param name="minValue">Minimum value of the generate integer.</param>
/// <param name="maxValue">Maximum value of the generated integer.</param>
/// <returns>Generated 32-bit integer.</returns>
public override int Next(int minValue, int maxValue)
=> this.GetInt32(minValue, maxValue);
/// <summary>
/// Generates a 64-bit floating-point number between 0.0 and 1.0. Upper end exclusive.
/// </summary>
/// <returns>Generated 64-bit floating-point number.</returns>
public override double NextDouble()
=> this.GetDouble();
/// <summary>
/// Fills specified buffer with random bytes.
/// </summary>
/// <param name="buffer">Buffer to fill with bytes.</param>
public override void NextBytes(byte[] buffer)
=> this.GetBytes(buffer);
/// <summary>
/// Fills specified memory region with random bytes.
/// </summary>
/// <param name="buffer">Memory region to fill with bytes.</param>
public new void NextBytes(Span<byte> buffer)
=> this.GetBytes(buffer);
/// <summary>
/// Disposes this <see cref="SecureRandom"/> instance and its resources.
/// </summary>
public void Dispose()
{
if (this._isDisposed)
return;
this._isDisposed = true;
this._rng.Dispose();
GC.SuppressFinalize(this);
}
/// <summary>
/// Generates a random 64-bit floating-point number between 0.0 and 1.0. Upper end exclusive.
/// </summary>
/// <returns>Generated 64-bit floating-point number.</returns>
protected override double Sample()
=> this.GetDouble();
/// <summary>
/// Generates the.
/// </summary>
/// <returns>A T.</returns>
private T Generate<T>() where T : struct
{
var size = Unsafe.SizeOf<T>();
Span<byte> buff = stackalloc byte[size];
this.GetBytes(buff);
return MemoryMarshal.Read<T>(buff);
}
}
diff --git a/DisCatSharp.Common/Types/Serialization/ComplexDecomposer.cs b/DisCatSharp.Common/Types/Serialization/ComplexDecomposer.cs
index b6c5e5e39..24e00bbbc 100644
--- a/DisCatSharp.Common/Types/Serialization/ComplexDecomposer.cs
+++ b/DisCatSharp.Common/Types/Serialization/ComplexDecomposer.cs
@@ -1,114 +1,114 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Numerics;
namespace DisCatSharp.Common.Serialization;
/// <summary>
/// Decomposes <see cref="Complex"/> numbers into tuples (arrays of 2).
/// </summary>
public sealed class ComplexDecomposer : IDecomposer
{
/// <summary>
/// Gets the t complex.
/// </summary>
private static Type s_complex { get; } = typeof(Complex);
/// <summary>
/// Gets the t double array.
/// </summary>
private static Type s_doubleArray { get; } = typeof(double[]);
/// <summary>
/// Gets the t double enumerable.
/// </summary>
private static Type s_doubleEnumerable { get; } = typeof(IEnumerable<double>);
/// <summary>
/// Gets the t object array.
/// </summary>
private static Type s_objectArray { get; } = typeof(object[]);
/// <summary>
/// Gets the t object enumerable.
/// </summary>
private static Type s_objectEnumerable { get; } = typeof(IEnumerable<object>);
/// <inheritdoc />
public bool CanDecompose(Type t)
=> t == s_complex;
/// <inheritdoc />
public bool CanRecompose(Type t)
=> t == s_doubleArray
|| t == s_objectArray
|| s_doubleEnumerable.IsAssignableFrom(t)
|| s_objectEnumerable.IsAssignableFrom(t);
/// <inheritdoc />
public bool TryDecompose(object obj, Type tobj, out object decomposed, out Type tdecomposed)
{
decomposed = null;
tdecomposed = s_doubleArray;
if (tobj != s_complex || obj is not Complex c)
return false;
decomposed = new[] { c.Real, c.Imaginary };
return true;
}
/// <inheritdoc />
public bool TryRecompose(object obj, Type tobj, Type trecomposed, out object recomposed)
{
recomposed = null;
if (trecomposed != s_complex)
return false;
// ie<double>
if (s_doubleEnumerable.IsAssignableFrom(tobj) && obj is IEnumerable<double> ied)
{
if (!ied.TryFirstTwo(out var values))
return false;
var (real, imag) = values;
recomposed = new Complex(real, imag);
return true;
}
// ie<obj>
if (s_objectEnumerable.IsAssignableFrom(tobj) && obj is IEnumerable<object> ieo)
{
if (!ieo.TryFirstTwo(out var values))
return false;
var (real, imag) = values;
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.Common/Types/Serialization/IDecomposer.cs b/DisCatSharp.Common/Types/Serialization/IDecomposer.cs
index 7c4519421..aecac0e02 100644
--- a/DisCatSharp.Common/Types/Serialization/IDecomposer.cs
+++ b/DisCatSharp.Common/Types/Serialization/IDecomposer.cs
@@ -1,66 +1,66 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Common.Serialization;
/// <summary>
/// Provides an interface to decompose an object into another object or combination of objects.
/// </summary>
public interface IDecomposer
{
/// <summary>
/// Checks whether the decomposer can decompose a specific type.
/// </summary>
/// <param name="t">Type to check.</param>
/// <returns>Whether the decomposer can decompose a given type.</returns>
bool CanDecompose(Type t);
/// <summary>
/// <para>Checks whether the decomposer can recompose a specific decomposed type.</para>
/// <para>Note that while a type might be considered recomposable, other factors might prevent recomposing operation from being successful.</para>
/// </summary>
/// <param name="t">Decomposed type to check.</param>
/// <returns>Whether the decomposer can decompose a given type.</returns>
bool CanRecompose(Type t);
/// <summary>
/// Attempts to decompose a given object of specified source type. The operation produces the decomposed object and the type it got decomposed into.
/// </summary>
/// <param name="obj">Object to decompose.</param>
/// <param name="tobj">Type to decompose.</param>
/// <param name="decomposed">Decomposition result.</param>
/// <param name="tdecomposed">Type of the result.</param>
/// <returns>Whether the operation was successful.</returns>
bool TryDecompose(object obj, Type tobj, out object decomposed, out Type tdecomposed);
/// <summary>
/// Attempts to recompose given object of specified source type, into specified target type. The operation produces the recomposed object.
/// </summary>
/// <param name="obj">Object to recompose from.</param>
/// <param name="tobj">Type of data to recompose.</param>
/// <param name="trecomposed">Type to recompose into.</param>
/// <param name="recomposed">Recomposition result.</param>
/// <returns>Whether the operation was successful.</returns>
bool TryRecompose(object obj, Type tobj, Type trecomposed, out object recomposed);
}
diff --git a/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEvent.cs b/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEvent.cs
index d838d23af..90b408ba9 100644
--- a/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEvent.cs
+++ b/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEvent.cs
@@ -1,199 +1,199 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Immutable;
using System.Threading.Tasks;
namespace DisCatSharp.Common.Utilities;
/// <summary>
/// ABC for <see cref="AsyncEvent{TSender, TArgs}"/>, allowing for using instances thereof without knowing the underlying instance's type parameters.
/// </summary>
public abstract class AsyncEvent
{
/// <summary>
/// Gets the name of this event.
/// </summary>
public string Name { get; }
/// <summary>
/// Prevents a default instance of the <see cref="AsyncEvent"/> class from being created.
/// </summary>
/// <param name="name">The name.</param>
private protected AsyncEvent(string name)
{
this.Name = name;
}
}
/// <summary>
/// Implementation of asynchronous event. The handlers of such events are executed asynchronously, but sequentially.
/// </summary>
/// <typeparam name="TSender">Type of the object that dispatches this event.</typeparam>
/// <typeparam name="TArgs">Type of event argument object passed to this event's handlers.</typeparam>
public sealed class AsyncEvent<TSender, TArgs> : AsyncEvent
where TArgs : AsyncEventArgs
{
/// <summary>
/// Gets the maximum allotted execution time for all handlers. Any event which causes the handler to time out
/// will raise a non-fatal <see cref="AsyncEventTimeoutException{TSender, TArgs}"/>.
/// </summary>
public TimeSpan MaximumExecutionTime { get; }
private readonly object _lock = new();
private ImmutableArray<AsyncEventHandler<TSender, TArgs>> _handlers;
private readonly AsyncEventExceptionHandler<TSender, TArgs> _exceptionHandler;
/// <summary>
/// Creates a new asynchronous event with specified name and exception handler.
/// </summary>
/// <param name="name">Name of this event.</param>
/// <param name="maxExecutionTime">Maximum handler execution time. A value of <see cref="TimeSpan.Zero"/> means infinite.</param>
/// <param name="exceptionHandler">Delegate which handles exceptions caused by this event.</param>
public AsyncEvent(string name, TimeSpan maxExecutionTime, AsyncEventExceptionHandler<TSender, TArgs> exceptionHandler)
: base(name)
{
this._handlers = ImmutableArray<AsyncEventHandler<TSender, TArgs>>.Empty;
this._exceptionHandler = exceptionHandler;
this.MaximumExecutionTime = maxExecutionTime;
}
/// <summary>
/// Registers a new handler for this event.
/// </summary>
/// <param name="handler">Handler to register for this event.</param>
public void Register(AsyncEventHandler<TSender, TArgs> handler)
{
if (handler == null)
throw new ArgumentNullException(nameof(handler));
lock (this._lock)
this._handlers = this._handlers.Add(handler);
}
/// <summary>
/// Unregisters an existing handler from this event.
/// </summary>
/// <param name="handler">Handler to unregister from the event.</param>
public void Unregister(AsyncEventHandler<TSender, TArgs> handler)
{
if (handler == null)
throw new ArgumentNullException(nameof(handler));
lock (this._lock)
this._handlers = this._handlers.Remove(handler);
}
/// <summary>
/// Unregisters all existing handlers from this event.
/// </summary>
public void UnregisterAll() => this._handlers = ImmutableArray<AsyncEventHandler<TSender, TArgs>>.Empty;
/// <summary>
/// <para>Raises this event by invoking all of its registered handlers, in order of registration.</para>
/// <para>All exceptions throw during invocation will be handled by the event's registered exception handler.</para>
/// </summary>
/// <param name="sender">Object which raised this event.</param>
/// <param name="e">Arguments for this event.</param>
/// <param name="exceptionMode">Defines what to do with exceptions caught from handlers.</param>
/// <returns></returns>
public async Task InvokeAsync(TSender sender, TArgs e, AsyncEventExceptionMode exceptionMode = AsyncEventExceptionMode.Default)
{
var handlers = this._handlers;
if (handlers.Length == 0)
return;
// Collect exceptions
List<Exception> exceptions = null;
if ((exceptionMode & AsyncEventExceptionMode.ThrowAll) != 0)
exceptions = new List<Exception>(handlers.Length * 2 /* timeout + regular */);
// If we have a timeout configured, start the timeout task
var timeout = this.MaximumExecutionTime > TimeSpan.Zero ? Task.Delay(this.MaximumExecutionTime) : null;
foreach (var handler in handlers)
{
try
{
// Start the handler execution
var handlerTask = handler(sender, e);
if (handlerTask != null && timeout != null)
{
// If timeout is configured, wait for any task to finish
// If the timeout task finishes first, the handler is causing a timeout
var result = await Task.WhenAny(timeout, handlerTask).ConfigureAwait(false);
if (result == timeout)
{
timeout = null;
var timeoutEx = new AsyncEventTimeoutException<TSender, TArgs>(this, handler);
// Notify about the timeout and complete execution
if ((exceptionMode & AsyncEventExceptionMode.HandleNonFatal) == AsyncEventExceptionMode.HandleNonFatal)
this.HandleException(timeoutEx, handler, sender, e);
if ((exceptionMode & AsyncEventExceptionMode.ThrowNonFatal) == AsyncEventExceptionMode.ThrowNonFatal)
exceptions.Add(timeoutEx);
await handlerTask.ConfigureAwait(false);
}
}
else if (handlerTask != null)
{
// No timeout is configured, or timeout already expired, proceed as usual
await handlerTask.ConfigureAwait(false);
}
if (e.Handled)
break;
}
catch (Exception ex)
{
e.Handled = false;
if ((exceptionMode & AsyncEventExceptionMode.HandleFatal) == AsyncEventExceptionMode.HandleFatal)
this.HandleException(ex, handler, sender, e);
if ((exceptionMode & AsyncEventExceptionMode.ThrowFatal) == AsyncEventExceptionMode.ThrowFatal)
exceptions.Add(ex);
}
}
if ((exceptionMode & AsyncEventExceptionMode.ThrowAll) != 0 && exceptions.Count > 0)
throw new AggregateException("Exceptions were thrown during execution of the event's handlers.", exceptions);
}
/// <summary>
/// Handles the exception.
/// </summary>
/// <param name="ex">The ex.</param>
/// <param name="handler">The handler.</param>
/// <param name="sender">The sender.</param>
/// <param name="args">The args.</param>
private void HandleException(Exception ex, AsyncEventHandler<TSender, TArgs> handler, TSender sender, TArgs args)
{
if (this._exceptionHandler != null)
this._exceptionHandler(this, ex, handler, sender, args);
}
}
diff --git a/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEventArgs.cs b/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEventArgs.cs
index 3e0d6551f..6786ce152 100644
--- a/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEventArgs.cs
+++ b/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEventArgs.cs
@@ -1,37 +1,37 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Common.Utilities;
/// <summary>
/// Contains arguments passed to an asynchronous event.
/// </summary>
public class AsyncEventArgs : EventArgs
{
/// <summary>
/// <para>Gets or sets whether this event was handled.</para>
/// <para>Setting this to true will prevent other handlers from running.</para>
/// </summary>
public bool Handled { get; set; } = false;
}
diff --git a/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEventExceptionHandler.cs b/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEventExceptionHandler.cs
index 71b767152..60a4554b2 100644
--- a/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEventExceptionHandler.cs
+++ b/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEventExceptionHandler.cs
@@ -1,38 +1,38 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Common.Utilities;
/// <summary>
/// Handles any exception raised by an <see cref="AsyncEvent{TSender, TArgs}"/> or its handlers.
/// </summary>
/// <typeparam name="TSender">Type of the object that dispatches this event.</typeparam>
/// <typeparam name="TArgs">Type of the object which holds arguments for this event.</typeparam>
/// <param name="asyncEvent">Asynchronous event which threw the exception.</param>
/// <param name="exception">Exception that was thrown</param>
/// <param name="handler">Handler which threw the exception.</param>
/// <param name="sender">Object which dispatched the event.</param>
/// <param name="eventArgs">Arguments with which the event was dispatched.</param>
public delegate void AsyncEventExceptionHandler<TSender, TArgs>(AsyncEvent<TSender, TArgs> asyncEvent, Exception exception, AsyncEventHandler<TSender, TArgs> handler, TSender sender, TArgs eventArgs)
where TArgs : AsyncEventArgs;
diff --git a/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEventExceptionMode.cs b/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEventExceptionMode.cs
index 5857024ff..d5c8afdac 100644
--- a/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEventExceptionMode.cs
+++ b/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEventExceptionMode.cs
@@ -1,81 +1,81 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Common.Utilities;
/// <summary>
/// Defines the behaviour for throwing exceptions from <see cref="AsyncEvent{TSender, TArgs}.InvokeAsync(TSender, TArgs, AsyncEventExceptionMode)"/>.
/// </summary>
public enum AsyncEventExceptionMode : int
{
/// <summary>
/// Defines that no exceptions should be thrown. Only exception handlers will be used.
/// </summary>
IgnoreAll = 0,
/// <summary>
/// Defines that only fatal (i.e. non-<see cref="AsyncEventTimeoutException{TSender, TArgs}"/>) exceptions
/// should be thrown.
/// </summary>
ThrowFatal = 1,
/// <summary>
/// Defines that only non-fatal (i.e. <see cref="AsyncEventTimeoutException{TSender, TArgs}"/>) exceptions
/// should be thrown.
/// </summary>
ThrowNonFatal = 2,
/// <summary>
/// Defines that all exceptions should be thrown. This is equivalent to combining <see cref="ThrowFatal"/> and
/// <see cref="ThrowNonFatal"/> flags.
/// </summary>
ThrowAll = ThrowFatal | ThrowNonFatal,
/// <summary>
/// Defines that only fatal (i.e. non-<see cref="AsyncEventTimeoutException{TSender, TArgs}"/>) exceptions
/// should be handled by the specified exception handler.
/// </summary>
HandleFatal = 4,
/// <summary>
/// Defines that only non-fatal (i.e. <see cref="AsyncEventTimeoutException{TSender, TArgs}"/>) exceptions
/// should be handled by the specified exception handler.
/// </summary>
HandleNonFatal = 8,
/// <summary>
/// Defines that all exceptions should be handled by the specified exception handler. This is equivalent to
/// combining <see cref="HandleFatal"/> and <see cref="HandleNonFatal"/> flags.
/// </summary>
HandleAll = HandleFatal | HandleNonFatal,
/// <summary>
/// Defines that all exceptions should be thrown and handled by the specified exception handler. This is
/// equivalent to combining <see cref="HandleAll"/> and <see cref="ThrowAll"/> flags.
/// </summary>
ThrowAllHandleAll = ThrowAll | HandleAll,
/// <summary>
/// Default mode, equivalent to <see cref="HandleAll"/>.
/// </summary>
Default = HandleAll
}
diff --git a/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEventHandler.cs b/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEventHandler.cs
index 2cd967bc7..04a0dd758 100644
--- a/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEventHandler.cs
+++ b/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEventHandler.cs
@@ -1,35 +1,35 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Threading.Tasks;
namespace DisCatSharp.Common.Utilities;
/// <summary>
/// Handles an asynchronous event of type <see cref="AsyncEvent{TSender, TArgs}"/>. The handler will take an instance of <typeparamref name="TArgs"/> as its arguments.
/// </summary>
/// <typeparam name="TSender">Type of the object that dispatches this event.</typeparam>
/// <typeparam name="TArgs">Type of the object which holds arguments for this event.</typeparam>
/// <param name="sender">Object which raised this event.</param>
/// <param name="e">Arguments for this event.</param>
/// <returns></returns>
public delegate Task AsyncEventHandler<in TSender, in TArgs>(TSender sender, TArgs e) where TArgs : AsyncEventArgs;
diff --git a/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEventTimeoutException.cs b/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEventTimeoutException.cs
index 0c5c2e021..55d597f9a 100644
--- a/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEventTimeoutException.cs
+++ b/DisCatSharp.Common/Utilities/AsyncEvent/AsyncEventTimeoutException.cs
@@ -1,83 +1,83 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Common.Utilities;
/// <summary>
/// ABC for <see cref="AsyncEventHandler{TSender, TArgs}"/>, allowing for using instances thereof without knowing the underlying instance's type parameters.
/// </summary>
public abstract class AsyncEventTimeoutException : Exception
{
/// <summary>
/// Gets the event the invocation of which caused the timeout.
/// </summary>
public AsyncEvent Event { get; }
/// <summary>
/// Gets the handler which caused the timeout.
/// </summary>
public AsyncEventHandler<object, AsyncEventArgs> Handler { get; }
/// <summary>
/// Prevents a default instance of the <see cref="AsyncEventTimeoutException"/> class from being created.
/// </summary>
/// <param name="asyncEvent">The async event.</param>
/// <param name="eventHandler">The event handler.</param>
/// <param name="message">The message.</param>
private protected AsyncEventTimeoutException(AsyncEvent asyncEvent, AsyncEventHandler<object, AsyncEventArgs> eventHandler, string message)
: base(message)
{
this.Event = asyncEvent;
this.Handler = eventHandler;
}
}
/// <summary>
/// <para>Thrown whenever execution of an <see cref="AsyncEventHandler{TSender, TArgs}"/> exceeds maximum time allowed.</para>
/// <para>This is a non-fatal exception, used primarily to inform users that their code is taking too long to execute.</para>
/// </summary>
/// <typeparam name="TSender">Type of sender that dispatched this asynchronous event.</typeparam>
/// <typeparam name="TArgs">Type of event arguments for the asynchronous event.</typeparam>
public class AsyncEventTimeoutException<TSender, TArgs> : AsyncEventTimeoutException
where TArgs : AsyncEventArgs
{
/// <summary>
/// Gets the event the invocation of which caused the timeout.
/// </summary>
public new AsyncEvent<TSender, TArgs> Event => base.Event as AsyncEvent<TSender, TArgs>;
/// <summary>
/// Gets the handler which caused the timeout.
/// </summary>
public new AsyncEventHandler<TSender, TArgs> Handler => base.Handler as AsyncEventHandler<TSender, TArgs>;
/// <summary>
/// Creates a new timeout exception for specified event and handler.
/// </summary>
/// <param name="asyncEvent">Event the execution of which timed out.</param>
/// <param name="eventHandler">Handler which timed out.</param>
public AsyncEventTimeoutException(AsyncEvent<TSender, TArgs> asyncEvent, AsyncEventHandler<TSender, TArgs> eventHandler)
: base(asyncEvent, eventHandler as AsyncEventHandler<object, AsyncEventArgs>, "An event handler caused the invocation of an asynchronous event to time out.")
{ }
}
diff --git a/DisCatSharp.Common/Utilities/AsyncExecutor.cs b/DisCatSharp.Common/Utilities/AsyncExecutor.cs
index 671ee3c00..28722fbee 100644
--- a/DisCatSharp.Common/Utilities/AsyncExecutor.cs
+++ b/DisCatSharp.Common/Utilities/AsyncExecutor.cs
@@ -1,172 +1,172 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
namespace DisCatSharp.Common.Utilities;
/// <summary>
/// Provides a simplified way of executing asynchronous code synchronously.
/// </summary>
public class AsyncExecutor
{
/// <summary>
/// Creates a new instance of asynchronous executor.
/// </summary>
public AsyncExecutor()
{ }
/// <summary>
/// Executes a specified task in an asynchronous manner, waiting for its completion.
/// </summary>
/// <param name="task">Task to execute.</param>
public void Execute(Task task)
{
// create state object
var taskState = new StateRef<object>(new AutoResetEvent(false));
// queue a task and wait for it to finish executing
task.ContinueWith(TaskCompletionHandler, taskState);
taskState.Lock.WaitOne();
// check for and rethrow any exceptions
if (taskState.Exception != null)
throw taskState.Exception;
// completion method
void TaskCompletionHandler(Task t, object state)
{
// retrieve state data
var stateRef = state as StateRef<object>;
// retrieve any exceptions or cancellation status
if (t.IsFaulted)
{
if (t.Exception.InnerExceptions.Count == 1) // unwrap if 1
stateRef.Exception = t.Exception.InnerException;
else
stateRef.Exception = t.Exception;
}
else if (t.IsCanceled)
{
stateRef.Exception = new TaskCanceledException(t);
}
// signal that the execution is done
stateRef.Lock.Set();
}
}
/// <summary>
/// Executes a specified task in an asynchronous manner, waiting for its completion, and returning the result.
/// </summary>
/// <typeparam name="T">Type of the Task's return value.</typeparam>
/// <param name="task">Task to execute.</param>
/// <returns>Task's result.</returns>
public T Execute<T>(Task<T> task)
{
// create state object
var taskState = new StateRef<T>(new AutoResetEvent(false));
// queue a task and wait for it to finish executing
task.ContinueWith(TaskCompletionHandler, taskState);
taskState.Lock.WaitOne();
// check for and rethrow any exceptions
if (taskState.Exception != null)
throw taskState.Exception;
// return the result, if any
if (taskState.HasResult)
return taskState.Result;
// throw exception if no result
throw new Exception("Task returned no result.");
// completion method
void TaskCompletionHandler(Task<T> t, object state)
{
// retrieve state data
var stateRef = state as StateRef<T>;
// retrieve any exceptions or cancellation status
if (t.IsFaulted)
{
if (t.Exception.InnerExceptions.Count == 1) // unwrap if 1
stateRef.Exception = t.Exception.InnerException;
else
stateRef.Exception = t.Exception;
}
else if (t.IsCanceled)
{
stateRef.Exception = new TaskCanceledException(t);
}
// return the result from the task, if any
if (t.IsCompleted && !t.IsFaulted)
{
stateRef.HasResult = true;
stateRef.Result = t.Result;
}
// signal that the execution is done
stateRef.Lock.Set();
}
}
/// <summary>
/// The state ref.
/// </summary>
private sealed class StateRef<T>
{
/// <summary>
/// Gets the lock used to wait for task's completion.
/// </summary>
public AutoResetEvent Lock { get; }
/// <summary>
/// Gets the exception that occurred during task's execution, if any.
/// </summary>
public Exception Exception { get; set; }
/// <summary>
/// Gets the result returned by the task.
/// </summary>
public T Result { get; set; }
/// <summary>
/// Gets whether the task returned a result.
/// </summary>
public bool HasResult { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="StateRef{T}"/> class.
/// </summary>
/// <param name="lock">The lock.</param>
public StateRef(AutoResetEvent @lock)
{
this.Lock = @lock;
}
}
}
diff --git a/DisCatSharp.Common/Utilities/AsyncManualResetEvent.cs b/DisCatSharp.Common/Utilities/AsyncManualResetEvent.cs
index 129a9fdb1..966934918 100644
--- a/DisCatSharp.Common/Utilities/AsyncManualResetEvent.cs
+++ b/DisCatSharp.Common/Utilities/AsyncManualResetEvent.cs
@@ -1,80 +1,80 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Threading;
using System.Threading.Tasks;
namespace DisCatSharp.Common.Utilities;
/// <summary>
/// Represents a thread synchronization event that, when signaled, must be reset manually. Unlike <see cref="ManualResetEventSlim"/>, this event is asynchronous.
/// </summary>
public sealed class AsyncManualResetEvent
{
/// <summary>
/// Gets whether this event has been signaled.
/// </summary>
public bool IsSet => this._resetTcs?.Task?.IsCompleted == true;
private volatile TaskCompletionSource<bool> _resetTcs;
/// <summary>
/// Creates a new asynchronous synchronization event with initial state.
/// </summary>
/// <param name="initialState">Initial state of this event.</param>
public AsyncManualResetEvent(bool initialState)
{
this._resetTcs = new TaskCompletionSource<bool>();
if (initialState)
this._resetTcs.TrySetResult(initialState);
}
// Spawn a threadpool thread instead of making a task
// Maybe overkill, but I am less unsure of this than awaits and
// potentially cross-scheduler interactions
/// <summary>
/// Asynchronously signal this event.
/// </summary>
/// <returns></returns>
public Task SetAsync()
=> Task.Run(() => this._resetTcs.TrySetResult(true));
/// <summary>
/// Asynchronously wait for this event to be signaled.
/// </summary>
/// <returns></returns>
public Task WaitAsync()
=> this._resetTcs.Task;
/// <summary>
/// Reset this event's signal state to unsignaled.
/// </summary>
public void Reset()
{
while (true)
{
var tcs = this._resetTcs;
if (!tcs.Task.IsCompleted || Interlocked.CompareExchange(ref this._resetTcs, new TaskCompletionSource<bool>(), tcs) == tcs)
return;
}
}
}
diff --git a/DisCatSharp.Common/Utilities/EnsureObjectStates.cs b/DisCatSharp.Common/Utilities/EnsureObjectStates.cs
index 0e71821a7..89893ab0b 100644
--- a/DisCatSharp.Common/Utilities/EnsureObjectStates.cs
+++ b/DisCatSharp.Common/Utilities/EnsureObjectStates.cs
@@ -1,70 +1,70 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
namespace DisCatSharp.Common;
/// <summary>
/// Ensures that certain objects have the target state.
/// </summary>
public static class EnsureObjectStates
{
/// <summary>
/// Checks whether the dictionary is null or empty.
/// </summary>
/// <typeparam name="T1">Any key type.</typeparam>
/// <typeparam name="T2">Any value type.</typeparam>
/// <param name="dictionary">The dictionary to check on.</param>
/// <returns>True if satisfied, false otherwise.</returns>
public static bool EmptyOrNull<T1, T2>(this Dictionary<T1, T2?>? dictionary) where T1 : notnull
=> dictionary == null || !dictionary.Any() || !dictionary.Keys.Any();
/// <summary>
/// Checks whether the dictionary is not null and not empty.
/// </summary>
/// <typeparam name="T1">Any key type.</typeparam>
/// <typeparam name="T2">Any value type.</typeparam>
/// <param name="dictionary">The dictionary to check on.</param>
/// <returns>True if satisfied, false otherwise.</returns>
public static bool NotEmptyAndNotNull<T1, T2>(this Dictionary<T1, T2?>? dictionary) where T1 : notnull
=> dictionary != null && dictionary.Any() && dictionary.Keys.Any();
/// <summary>
/// Checks whether the list is null or empty.
/// </summary>
/// <typeparam name="T">Any value type.</typeparam>
/// <param name="list">The list to check on.</param>
/// <returns>True if satisfied, false otherwise.</returns>
public static bool EmptyOrNull<T>(this List<T?>? list)
=> list == null || !list.Any();
/// <summary>
/// Checks whether the list is not null and not empty.
/// </summary>
/// <typeparam name="T">Any value type.</typeparam>
/// <param name="list">The list to check on.</param>
/// <returns>True if satisfied, false otherwise.</returns>
public static bool NotEmptyAndNotNull<T>(this List<T?>? list)
=> list != null && list.Any();
}
diff --git a/DisCatSharp.Common/Utilities/Extensions.cs b/DisCatSharp.Common/Utilities/Extensions.cs
index ce4dee7a5..b76d5f71f 100644
--- a/DisCatSharp.Common/Utilities/Extensions.cs
+++ b/DisCatSharp.Common/Utilities/Extensions.cs
@@ -1,530 +1,530 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Runtime.CompilerServices;
namespace DisCatSharp.Common;
/// <summary>
/// Assortment of various extension and utility methods, designed to make working with various types a little easier.
/// </summary>
public static class Extensions
{
/// <summary>
/// <para>Deconstructs a <see cref="Dictionary{TKey, TValue}"/> key-value pair item (<see cref="KeyValuePair{TKey, TValue}"/>) into 2 separate variables.</para>
/// <para>This allows for enumerating over dictionaries in foreach blocks by using a (k, v) tuple as the enumerator variable, instead of having to use a <see cref="KeyValuePair{TKey, TValue}"/> directly.</para>
/// </summary>
/// <typeparam name="TKey">Type of dictionary item key.</typeparam>
/// <typeparam name="TValue">Type of dictionary item value.</typeparam>
/// <param name="kvp">Key-value pair to deconstruct.</param>
/// <param name="key">Deconstructed key.</param>
/// <param name="value">Deconstructed value.</param>
public static void Deconstruct<TKey, TValue>(this KeyValuePair<TKey, TValue> kvp, out TKey key, out TValue value)
{
key = kvp.Key;
value = kvp.Value;
}
/// <summary>
/// Calculates the length of string representation of given number in base 10 (including sign, if present).
/// </summary>
/// <param name="num">Number to calculate the length of.</param>
/// <returns>Calculated number length.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateLength(this sbyte num)
=> num == 0 ? 1 : (int)Math.Floor(Math.Log10(Math.Abs(num == sbyte.MinValue ? num + 1 : num))) + (num < 0 ? 2 /* include sign */ : 1);
/// <summary>
/// Calculates the length of string representation of given number in base 10 (including sign, if present).
/// </summary>
/// <param name="num">Number to calculate the length of.</param>
/// <returns>Calculated number length.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateLength(this byte num)
=> num == 0 ? 1 : (int)Math.Floor(Math.Log10(num)) + 1;
/// <summary>
/// Calculates the length of string representation of given number in base 10 (including sign, if present).
/// </summary>
/// <param name="num">Number to calculate the length of.</param>
/// <returns>Calculated number length.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateLength(this short num)
=> num == 0 ? 1 : (int)Math.Floor(Math.Log10(Math.Abs(num == short.MinValue ? num + 1 : num))) + (num < 0 ? 2 /* include sign */ : 1);
/// <summary>
/// Calculates the length of string representation of given number in base 10 (including sign, if present).
/// </summary>
/// <param name="num">Number to calculate the length of.</param>
/// <returns>Calculated number length.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateLength(this ushort num)
=> num == 0 ? 1 : (int)Math.Floor(Math.Log10(num)) + 1;
/// <summary>
/// Calculates the length of string representation of given number in base 10 (including sign, if present).
/// </summary>
/// <param name="num">Number to calculate the length of.</param>
/// <returns>Calculated number length.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateLength(this int num)
=> num == 0 ? 1 : (int)Math.Floor(Math.Log10(Math.Abs(num == int.MinValue ? num + 1 : num))) + (num < 0 ? 2 /* include sign */ : 1);
/// <summary>
/// Calculates the length of string representation of given number in base 10 (including sign, if present).
/// </summary>
/// <param name="num">Number to calculate the length of.</param>
/// <returns>Calculated number length.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateLength(this uint num)
=> num == 0 ? 1 : (int)Math.Floor(Math.Log10(num)) + 1;
/// <summary>
/// Calculates the length of string representation of given number in base 10 (including sign, if present).
/// </summary>
/// <param name="num">Number to calculate the length of.</param>
/// <returns>Calculated number length.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateLength(this long num)
=> num == 0 ? 1 : (int)Math.Floor(Math.Log10(Math.Abs(num == long.MinValue ? num + 1 : num))) + (num < 0 ? 2 /* include sign */ : 1);
/// <summary>
/// Calculates the length of string representation of given number in base 10 (including sign, if present).
/// </summary>
/// <param name="num">Number to calculate the length of.</param>
/// <returns>Calculated number length.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateLength(this ulong num)
=> num == 0 ? 1 : (int)Math.Floor(Math.Log10(num)) + 1;
/// <summary>
/// Tests whether given value is in supplied range, optionally allowing it to be an exclusive check.
/// </summary>
/// <param name="num">Number to test.</param>
/// <param name="min">Lower bound of the range.</param>
/// <param name="max">Upper bound of the range.</param>
/// <param name="inclusive">Whether the check is to be inclusive.</param>
/// <returns>Whether the value is in range.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this sbyte num, sbyte min, sbyte max, bool inclusive = true)
{
if (min > max)
{
min ^= max;
max ^= min;
min ^= max;
}
return inclusive ? num >= min && num <= max : num > min && num < max;
}
/// <summary>
/// Tests whether given value is in supplied range, optionally allowing it to be an exclusive check.
/// </summary>
/// <param name="num">Number to test.</param>
/// <param name="min">Lower bound of the range.</param>
/// <param name="max">Upper bound of the range.</param>
/// <param name="inclusive">Whether the check is to be inclusive.</param>
/// <returns>Whether the value is in range.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this byte num, byte min, byte max, bool inclusive = true)
{
if (min > max)
{
min ^= max;
max ^= min;
min ^= max;
}
return inclusive ? num >= min && num <= max : num > min && num < max;
}
/// <summary>
/// Tests whether given value is in supplied range, optionally allowing it to be an exclusive check.
/// </summary>
/// <param name="num">Number to test.</param>
/// <param name="min">Lower bound of the range.</param>
/// <param name="max">Upper bound of the range.</param>
/// <param name="inclusive">Whether the check is to be inclusive.</param>
/// <returns>Whether the value is in range.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this short num, short min, short max, bool inclusive = true)
{
if (min > max)
{
min ^= max;
max ^= min;
min ^= max;
}
return inclusive ? num >= min && num <= max : num > min && num < max;
}
/// <summary>
/// Tests whether given value is in supplied range, optionally allowing it to be an exclusive check.
/// </summary>
/// <param name="num">Number to test.</param>
/// <param name="min">Lower bound of the range.</param>
/// <param name="max">Upper bound of the range.</param>
/// <param name="inclusive">Whether the check is to be inclusive.</param>
/// <returns>Whether the value is in range.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this ushort num, ushort min, ushort max, bool inclusive = true)
{
if (min > max)
{
min ^= max;
max ^= min;
min ^= max;
}
return inclusive ? num >= min && num <= max : num > min && num < max;
}
/// <summary>
/// Tests whether given value is in supplied range, optionally allowing it to be an exclusive check.
/// </summary>
/// <param name="num">Number to test.</param>
/// <param name="min">Lower bound of the range.</param>
/// <param name="max">Upper bound of the range.</param>
/// <param name="inclusive">Whether the check is to be inclusive.</param>
/// <returns>Whether the value is in range.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this int num, int min, int max, bool inclusive = true)
{
if (min > max)
{
min ^= max;
max ^= min;
min ^= max;
}
return inclusive ? num >= min && num <= max : num > min && num < max;
}
/// <summary>
/// Tests whether given value is in supplied range, optionally allowing it to be an exclusive check.
/// </summary>
/// <param name="num">Number to test.</param>
/// <param name="min">Lower bound of the range.</param>
/// <param name="max">Upper bound of the range.</param>
/// <param name="inclusive">Whether the check is to be inclusive.</param>
/// <returns>Whether the value is in range.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this uint num, uint min, uint max, bool inclusive = true)
{
if (min > max)
{
min ^= max;
max ^= min;
min ^= max;
}
return inclusive ? num >= min && num <= max : num > min && num < max;
}
/// <summary>
/// Tests whether given value is in supplied range, optionally allowing it to be an exclusive check.
/// </summary>
/// <param name="num">Number to test.</param>
/// <param name="min">Lower bound of the range.</param>
/// <param name="max">Upper bound of the range.</param>
/// <param name="inclusive">Whether the check is to be inclusive.</param>
/// <returns>Whether the value is in range.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this long num, long min, long max, bool inclusive = true)
{
if (min > max)
{
min ^= max;
max ^= min;
min ^= max;
}
return inclusive ? num >= min && num <= max : num > min && num < max;
}
/// <summary>
/// Tests whether given value is in supplied range, optionally allowing it to be an exclusive check.
/// </summary>
/// <param name="num">Number to test.</param>
/// <param name="min">Lower bound of the range.</param>
/// <param name="max">Upper bound of the range.</param>
/// <param name="inclusive">Whether the check is to be inclusive.</param>
/// <returns>Whether the value is in range.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this ulong num, ulong min, ulong max, bool inclusive = true)
{
if (min > max)
{
min ^= max;
max ^= min;
min ^= max;
}
return inclusive ? num >= min && num <= max : num > min && num < max;
}
/// <summary>
/// Tests whether given value is in supplied range, optionally allowing it to be an exclusive check.
/// </summary>
/// <param name="num">Number to test.</param>
/// <param name="min">Lower bound of the range.</param>
/// <param name="max">Upper bound of the range.</param>
/// <param name="inclusive">Whether the check is to be inclusive.</param>
/// <returns>Whether the value is in range.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this float num, float min, float max, bool inclusive = true)
{
if (min > max)
return false;
return inclusive ? num >= min && num <= max : num > min && num < max;
}
/// <summary>
/// Tests whether given value is in supplied range, optionally allowing it to be an exclusive check.
/// </summary>
/// <param name="num">Number to test.</param>
/// <param name="min">Lower bound of the range.</param>
/// <param name="max">Upper bound of the range.</param>
/// <param name="inclusive">Whether the check is to be inclusive.</param>
/// <returns>Whether the value is in range.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRange(this double num, double min, double max, bool inclusive = true)
{
if (min > max)
return false;
return inclusive ? num >= min && num <= max : num > min && num < max;
}
/// <summary>
/// Returns whether supplied character is in any of the following ranges: a-z, A-Z, 0-9.
/// </summary>
/// <param name="c">Character to test.</param>
/// <returns>Whether the character is in basic alphanumeric character range.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsBasicAlphanumeric(this char c)
=> (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
/// <summary>
/// Returns whether supplied character is in the 0-9 range.
/// </summary>
/// <param name="c">Character to test.</param>
/// <returns>Whether the character is in basic numeric digit character range.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsBasicDigit(this char c)
=> c >= '0' && c <= '9';
/// <summary>
/// Returns whether supplied character is in the a-z or A-Z range.
/// </summary>
/// <param name="c">Character to test.</param>
/// <returns>Whether the character is in basic letter character range.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsBasicLetter(this char c)
=> (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
/// <summary>
/// Tests whether given string ends with given character.
/// </summary>
/// <param name="s">String to test.</param>
/// <param name="c">Character to test for.</param>
/// <returns>Whether the supplied string ends with supplied character.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool EndsWithCharacter(this string s, char c)
=> s.Length >= 1 && s[^1] == c;
/// <summary>
/// Tests whether given string starts with given character.
/// </summary>
/// <param name="s">String to test.</param>
/// <param name="c">Character to test for.</param>
/// <returns>Whether the supplied string starts with supplied character.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool StartsWithCharacter(this string s, char c)
=> s.Length >= 1 && s[0] == c;
// https://stackoverflow.com/questions/9545619/a-fast-hash-function-for-string-in-c-sharp
// Calls are inlined to call the underlying method directly
/// <summary>
/// Computes a 64-bit Knuth hash from supplied characters.
/// </summary>
/// <param name="chars">Characters to compute the hash value from.</param>
/// <returns>Computer 64-bit Knuth hash.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this ReadOnlySpan<char> chars)
=> Knuth(chars);
/// <summary>
/// Computes a 64-bit Knuth hash from supplied characters.
/// </summary>
/// <param name="chars">Characters to compute the hash value from.</param>
/// <returns>Computer 64-bit Knuth hash.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this Span<char> chars)
=> Knuth(chars);
/// <summary>
/// Computes a 64-bit Knuth hash from supplied characters.
/// </summary>
/// <param name="chars">Characters to compute the hash value from.</param>
/// <returns>Computer 64-bit Knuth hash.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this ReadOnlyMemory<char> chars)
=> Knuth(chars.Span);
/// <summary>
/// Computes a 64-bit Knuth hash from supplied characters.
/// </summary>
/// <param name="chars">Characters to compute the hash value from.</param>
/// <returns>Computer 64-bit Knuth hash.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this Memory<char> chars)
=> Knuth(chars.Span);
/// <summary>
/// Computes a 64-bit Knuth hash from supplied characters.
/// </summary>
/// <param name="chars">Characters to compute the hash value from.</param>
/// <returns>Computer 64-bit Knuth hash.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this ArraySegment<char> chars)
=> Knuth(chars.AsSpan());
/// <summary>
/// Computes a 64-bit Knuth hash from supplied characters.
/// </summary>
/// <param name="chars">Characters to compute the hash value from.</param>
/// <returns>Computer 64-bit Knuth hash.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this char[] chars)
=> Knuth(chars.AsSpan());
/// <summary>
/// Computes a 64-bit Knuth hash from supplied characters.
/// </summary>
/// <param name="chars">Characters to compute the hash value from.</param>
/// <param name="start">Offset in the array to start calculating from.</param>
/// <param name="count">Number of characters to compute the hash from.</param>
/// <returns>Computer 64-bit Knuth hash.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this char[] chars, int start, int count)
=> Knuth(chars.AsSpan(start, count));
/// <summary>
/// Computes a 64-bit Knuth hash from supplied characters.
/// </summary>
/// <param name="chars">Characters to compute the hash value from.</param>
/// <returns>Computer 64-bit Knuth hash.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this string chars)
=> Knuth(chars.AsSpan());
/// <summary>
/// Computes a 64-bit Knuth hash from supplied characters.
/// </summary>
/// <param name="chars">Characters to compute the hash value from.</param>
/// <param name="start">Offset in the array to start calculating from.</param>
/// <param name="count">Number of characters to compute the hash from.</param>
/// <returns>Computer 64-bit Knuth hash.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKnuthHash(this string chars, int start, int count)
=> Knuth(chars.AsSpan(start, count));
/// <summary>
/// Gets the two first elements of the <see cref="IEnumerable{T}"/>, if they exist.
/// </summary>
/// <param name="enumerable">The enumerable.</param>
/// <param name="values">The output values. Undefined if <code>false</code> is returned.</param>
/// <returns>Whether the <see cref="IEnumerable{T}"/> contained enough elements.</returns>
internal static bool TryFirstTwo<T>(this IEnumerable<T> enumerable, out (T first, T second) values)
{
values = default;
using var enumerator = enumerable.GetEnumerator();
if (!enumerator.MoveNext())
return false;
var first = enumerator.Current;
if (!enumerator.MoveNext())
return false;
values = (first, enumerator.Current);
return true;
}
/// <summary>
/// Knuths the.
/// </summary>
/// <param name="chars">The chars.</param>
/// <returns>An ulong.</returns>
private static ulong Knuth(ReadOnlySpan<char> chars)
{
var hash = 3074457345618258791ul;
foreach (var ch in chars)
hash = (hash + ch) * 3074457345618258799ul;
return hash;
}
/// <summary>
/// Removes the first item matching the predicate from the list.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list"></param>
/// <param name="predicate"></param>
/// <returns>Whether an item was removed.</returns>
internal static bool RemoveFirst<T>(this IList<T> list, Predicate<T> predicate)
{
for (var i = 0; i < list.Count; i++)
{
if (predicate(list[i]))
{
list.RemoveAt(i);
return true;
}
}
return false;
}
/// <summary>
/// Populates an array with the given value.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="arr"></param>
/// <param name="value"></param>
internal static void Populate<T>(this T[] arr, T value)
{
for (var i = 0; i < arr.Length; i++)
{
arr[i] = value;
}
}
}
diff --git a/DisCatSharp.Common/Utilities/ReflectionUtilities.cs b/DisCatSharp.Common/Utilities/ReflectionUtilities.cs
index b2729ca03..3d5e51cd7 100644
--- a/DisCatSharp.Common/Utilities/ReflectionUtilities.cs
+++ b/DisCatSharp.Common/Utilities/ReflectionUtilities.cs
@@ -1,74 +1,74 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Runtime.Serialization;
namespace DisCatSharp.Common.Utilities;
/// <summary>
/// Contains various utilities for use with .NET's reflection.
/// </summary>
public static class ReflectionUtilities
{
/// <summary>
/// <para>Creates an empty, uninitialized instance of specified type.</para>
/// <para>This method will not call the constructor for the specified type. As such, the object might not be properly initialized.</para>
/// </summary>
/// <remarks>
/// This method is intended for reflection use only.
/// </remarks>
/// <param name="t">Type of the object to instantiate.</param>
/// <returns>Empty, uninitialized object of specified type.</returns>
public static object CreateEmpty(this Type t)
=> FormatterServices.GetUninitializedObject(t);
/// <summary>
/// <para>Creates an empty, uninitialized instance of type <typeparamref name="T"/>.</para>
/// <para>This method will not call the constructor for type <typeparamref name="T"/>. As such, the object might not be properly initialized.</para>
/// </summary>
/// <remarks>
/// This method is intended for reflection use only.
/// </remarks>
/// <typeparam name="T">Type of the object to instantiate.</typeparam>
/// <returns>Empty, uninitialized object of specified type.</returns>
public static T CreateEmpty<T>()
=> (T)FormatterServices.GetUninitializedObject(typeof(T));
/// <summary>
/// Converts a given object into a dictionary of property name to property value mappings.
/// </summary>
/// <typeparam name="T">Type of object to convert.</typeparam>
/// <param name="obj">Object to convert.</param>
/// <returns>Converted dictionary.</returns>
public static IReadOnlyDictionary<string, object> ToDictionary<T>(this T obj)
{
if (obj == null)
throw new NullReferenceException();
return new CharSpanLookupReadOnlyDictionary<object>(typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Select(x => new KeyValuePair<string, object>(x.Name, x.GetValue(obj))));
}
}
diff --git a/DisCatSharp.Common/Utilities/RuntimeInformation.cs b/DisCatSharp.Common/Utilities/RuntimeInformation.cs
index e1de8c2c5..ffbe7aa40 100644
--- a/DisCatSharp.Common/Utilities/RuntimeInformation.cs
+++ b/DisCatSharp.Common/Utilities/RuntimeInformation.cs
@@ -1,77 +1,77 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Linq;
using System.Reflection;
using System.Text;
namespace DisCatSharp.Common.Utilities;
/// <summary>
/// Gets information about current runtime.
/// </summary>
public static class RuntimeInformation
{
/// <summary>
/// Gets the current runtime's version.
/// </summary>
public static string Version { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RuntimeInformation"/> class.
/// </summary>
static RuntimeInformation()
{
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
var mscorlib = loadedAssemblies.Select(x => new { Assembly = x, AssemblyName = x.GetName() })
.FirstOrDefault(x => x.AssemblyName.Name == "mscorlib" || x.AssemblyName.Name == "System.Private.CoreLib");
var location = mscorlib.Assembly.Location;
var assemblyFile = new FileInfo(location);
var versionFile = new FileInfo(Path.Combine(assemblyFile.Directory.FullName, ".version"));
if (versionFile.Exists)
{
var lines = File.ReadAllLines(versionFile.FullName, new UTF8Encoding(false));
if (lines.Length >= 2)
{
Version = lines[1];
return;
}
}
var infVersion = mscorlib.Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
if (infVersion != null)
{
var infVersionString = infVersion.InformationalVersion;
if (!string.IsNullOrWhiteSpace(infVersionString))
{
Version = infVersionString.Split(' ').First();
return;
}
}
Version = mscorlib.AssemblyName.Version.ToString();
}
}
diff --git a/DisCatSharp.Configuration.Tests/ConfigurationExtensionTests.cs b/DisCatSharp.Configuration.Tests/ConfigurationExtensionTests.cs
index 52b1768ad..8f7f3e5b0 100644
--- a/DisCatSharp.Configuration.Tests/ConfigurationExtensionTests.cs
+++ b/DisCatSharp.Configuration.Tests/ConfigurationExtensionTests.cs
@@ -1,337 +1,337 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.Configuration.Models;
using DisCatSharp.Enums;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Xunit;
namespace DisCatSharp.Configuration.Tests;
public class ConfigurationExtensionTests
{
#region Test Classes
class SampleClass
{
public int Amount { get; set; }
public string? Email { get; set; }
}
class ClassWithArray
{
public int[] Values { get; set; } = { 1, 2, 3, 4, 5 };
public string[] Strings { get; set; } = { "1", "2", "3", "4", "5" };
}
class ClassWithEnumerable
{
public IEnumerable<int> Values { get; set; } = new[] { 1, 2, 3, 4, 5 };
public IEnumerable<string> Strings { get; set; } = new[] { "1", "2", "3", "4", "5" };
}
class ClassWithList
{
public List<string> Strings { get; set; } = new()
{
"1",
"2",
"3",
"4",
"5"
};
public List<int> Values { get; set; } = new()
{
1,
2,
3,
4,
5
};
}
class SampleClass2
{
public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(7);
public string Name { get; set; } = "Sample";
public string ConstructorValue { get; }
public SampleClass2(string value)
{
this.ConstructorValue = value;
}
}
#endregion
private IConfiguration EnumerableTestConfiguration() =>
new ConfigurationBuilder()
.AddJsonFile("enumerable-test.json")
.Build();
private IConfiguration HasSectionWithSuffixConfiguration() =>
new ConfigurationBuilder()
.AddJsonFile("section-with-suffix.json")
.Build();
private IConfiguration HasSectionNoSuffixConfiguration() =>
new ConfigurationBuilder()
.AddJsonFile("section-no-suffix.json")
.Build();
private IConfiguration BasicDiscordConfiguration() => new ConfigurationBuilder()
.AddJsonFile("default-discord.json")
.Build();
private IConfiguration DiscordIntentsConfig() => new ConfigurationBuilder()
.AddJsonFile("intents-discord.json")
.Build();
private IConfiguration DiscordHaphazardConfig() => new ConfigurationBuilder()
.AddJsonFile("haphazard-discord.json")
.Build();
private IConfiguration SampleConfig() => new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>
{
{ "Sample:Amount", "200" },
{ "Sample:Email", "[email protected]" }
})
.Build();
private IConfiguration SampleClass2Configuration_Default() => new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>
{
{"Random:Stuff", "Meow"},
{"SampleClass2:Name", "Purfection"}
})
.Build();
private IConfiguration SampleClass2Configuration_Change() => new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>
{
{ "SampleClass:Timeout", "01:30:00" }, { "SampleClass:NotValid", "Something" }
})
.Build();
private IConfiguration SampleClass2EnumerableTest() => new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>
{
{ "SampleClass:EnumerableTest", "[\"10\",\"20\",\"30\"]" }
})
.Build();
private IConfiguration SampleClass2ArrayTest() => new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>
{
{ "SampleClass:ArrayTest", "[\"10\",\"20\",\"30\"]" }
})
.Build();
private IConfiguration SampleClass2ListTest() => new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>
{
{ "SampleClass:ListTest", "[\"10\",\"20\",\"30\"]" }
})
.Build();
[Fact]
public void TestExtractDiscordConfig_Intents()
{
var source = this.DiscordIntentsConfig();
var config = source.ExtractConfig<DiscordConfiguration>("Discord");
var expected = DiscordIntents.GuildEmojisAndStickers | DiscordIntents.GuildMembers |
DiscordIntents.GuildInvites | DiscordIntents.GuildMessageReactions;
Assert.Equal(expected, config.Intents);
}
[Fact]
public void TestExtractDiscordConfig_Haphazard()
{
var source = this.DiscordHaphazardConfig();
var config = source.ExtractConfig<DiscordConfiguration>("Discord");
var expectedIntents = DiscordIntents.GuildEmojisAndStickers | DiscordIntents.GuildMembers |
DiscordIntents.Guilds;
Assert.Equal(expectedIntents, config.Intents);
Assert.True(config.MobileStatus);
Assert.Equal(1000, config.LargeThreshold);
Assert.Equal(TimeSpan.FromHours(10), config.HttpTimeout);
}
[Fact]
public void TestExtractDiscordConfig_Default()
{
var source = this.BasicDiscordConfiguration();
var config = source.ExtractConfig<DiscordConfiguration>("Discord");
Assert.Equal("1234567890", config.Token);
Assert.Equal(TokenType.Bot, config.TokenType);
Assert.Equal(LogLevel.Information, config.MinimumLogLevel);
Assert.True(config.UseRelativeRatelimit);
Assert.Equal("yyyy-MM-dd HH:mm:ss zzz", config.LogTimestampFormat);
Assert.Equal(250, config.LargeThreshold);
Assert.True(config.AutoReconnect);
Assert.Equal(123123, config.ShardId);
Assert.Equal(GatewayCompressionLevel.Stream, config.GatewayCompressionLevel);
Assert.Equal(1024, config.MessageCacheSize);
Assert.Equal(TimeSpan.FromSeconds(20), config.HttpTimeout);
Assert.False(config.ReconnectIndefinitely);
Assert.True(config.AlwaysCacheMembers);
Assert.Equal(DiscordIntents.AllUnprivileged, config.Intents);
Assert.False(config.MobileStatus);
Assert.False(config.UseCanary);
Assert.False(config.AutoRefreshChannelCache);
}
[Fact]
public void TestSection()
{
var source = this.SampleConfig();
var config = source.ExtractConfig<SampleClass>("Sample", null);
Assert.Equal(200, config.Amount);
Assert.Equal("[email protected]", config.Email);
}
[Fact]
public void TestExtractConfig_V2_Default()
{
var source = this.SampleClass2Configuration_Default();
var config = (SampleClass2) source.ExtractConfig("SampleClass", () => new SampleClass2("Test"), null);
Assert.Equal(TimeSpan.FromMinutes(7), config.Timeout);
Assert.Equal("Test", config.ConstructorValue);
Assert.Equal("Sample", config.Name);
}
[Fact]
public void TestExtractConfig_V2_Change()
{
var source = this.SampleClass2Configuration_Change();
var config = (SampleClass2) source.ExtractConfig("SampleClass", () => new SampleClass2("Test123"), null);
var span = new TimeSpan(0, 1, 30, 0);
Assert.Equal(span, config.Timeout);
Assert.Equal("Test123", config.ConstructorValue);
Assert.Equal("Sample", config.Name);
}
[Fact]
public void TestExtractConfig_V3_Default()
{
var source = this.SampleClass2Configuration_Default();
var config =
(SampleClass2)new ConfigSection(ref source, "SampleClass", null).ExtractConfig(() =>
new SampleClass2("Meow"));
Assert.Equal("Meow", config.ConstructorValue);
Assert.Equal(TimeSpan.FromMinutes(7), config.Timeout);
Assert.Equal("Sample", config.Name);
}
[Fact]
public void TestExtractConfig_V3_Change()
{
var source = this.SampleClass2Configuration_Change();
var config =
(SampleClass2)new ConfigSection(ref source, "SampleClass", null).ExtractConfig(() =>
new SampleClass2("Meow"));
Assert.Equal("Meow", config.ConstructorValue);
var span = new TimeSpan(0, 1, 30, 0);
Assert.Equal(span, config.Timeout);
Assert.Equal("Sample", config.Name);
}
[Fact]
public void TestExtractConfig_Enumerable()
{
var source = this.EnumerableTestConfiguration();
var config =
(ClassWithEnumerable)new ConfigSection(ref source, "ClassWithEnumerable", null).ExtractConfig(() =>
new ClassWithEnumerable());
Assert.NotNull(config.Values);
Assert.Equal(3, config.Values.Count());
Assert.NotNull(config.Strings);
Assert.Equal(3, config.Values.Count());
}
[Fact]
public void TestExtractConfig_Array()
{
var source = this.EnumerableTestConfiguration();
var config =
(ClassWithArray)new ConfigSection(ref source, "ClassWithArray", null).ExtractConfig(() =>
new ClassWithArray());
Assert.NotNull(config.Values);
Assert.Equal(3, config.Values.Length);
Assert.NotNull(config.Strings);
Assert.Equal(3, config.Values.Length);
}
[Fact]
public void TestExtractConfig_List()
{
var source = this.EnumerableTestConfiguration();
var config =
(ClassWithList)new ConfigSection(ref source, "ClassWithList", null).ExtractConfig(() =>
new ClassWithList());
Assert.NotNull(config.Values);
Assert.Equal(3, config.Values.Count);
Assert.NotNull(config.Strings);
Assert.Equal(3, config.Values.Count);
}
[Fact]
public void TestHasSectionWithSuffix()
{
var source = this.HasSectionWithSuffixConfiguration();
Assert.True(source.HasSection("DiscordConfiguration"));
Assert.False(source.HasSection("Discord"));
#pragma warning disable 8625
Assert.False(source.HasSection("DiscordConfiguration", null));
#pragma warning restore 8625
}
[Fact]
public void TestHasSectionNoSuffix()
{
var source = this.HasSectionNoSuffixConfiguration();
Assert.True(source.HasSection("Discord"));
Assert.False(source.HasSection("DiscordConfiguration"));
#pragma warning disable 8625
Assert.False(source.HasSection("Discord", null));
#pragma warning restore 8625
}
}
diff --git a/DisCatSharp.Configuration.Tests/GlobalSuppressions.cs b/DisCatSharp.Configuration.Tests/GlobalSuppressions.cs
index 400a0fb5e..a3e03ecee 100644
--- a/DisCatSharp.Configuration.Tests/GlobalSuppressions.cs
+++ b/DisCatSharp.Configuration.Tests/GlobalSuppressions.cs
@@ -1,84 +1,84 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.BasicDiscordConfiguration~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.DiscordHaphazardConfig~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.DiscordIntentsConfig~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.EnumerableTestConfiguration~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.HasSectionNoSuffixConfiguration~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.HasSectionWithSuffixConfiguration~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.SampleClass2ArrayTest~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.SampleClass2ArrayTest~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.SampleClass2Configuration_Change~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.SampleClass2Configuration_Default~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.SampleClass2EnumerableTest~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.SampleClass2EnumerableTest~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.SampleClass2ListTest~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.SampleClass2ListTest~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.SampleConfig~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("DocumentationHeader", "ClassDocumentationHeader:The class must have a documentation header.", Justification = "<Pending>")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.BasicDiscordConfiguration~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.DiscordHaphazardConfig~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.DiscordIntentsConfig~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.EnumerableTestConfiguration~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.HasSectionNoSuffixConfiguration~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.HasSectionWithSuffixConfiguration~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("DocumentationHeader", "ConstructorDocumentationHeader:The constructor must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.SampleClass2.#ctor(System.String)")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.SampleClass2ArrayTest~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.SampleClass2Configuration_Change~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.SampleClass2Configuration_Default~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.SampleClass2EnumerableTest~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.SampleClass2ListTest~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.SampleConfig~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.TestExtractConfig_Array")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.TestExtractConfig_Enumerable")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.TestExtractConfig_List")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.TestExtractConfig_V2_Change")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.TestExtractConfig_V2_Default")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.TestExtractConfig_V3_Change")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.TestExtractConfig_V3_Default")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.TestExtractDiscordConfig_Default")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.TestExtractDiscordConfig_Haphazard")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.TestExtractDiscordConfig_Intents")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.TestHasSectionNoSuffix")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.TestHasSectionWithSuffix")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.TestSection")]
[assembly: SuppressMessage("DocumentationHeader", "PropertyDocumentationHeader:The property must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.ClassWithArray.Strings")]
[assembly: SuppressMessage("DocumentationHeader", "PropertyDocumentationHeader:The property must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.ClassWithArray.Values")]
[assembly: SuppressMessage("DocumentationHeader", "PropertyDocumentationHeader:The property must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.ClassWithEnumerable.Strings")]
[assembly: SuppressMessage("DocumentationHeader", "PropertyDocumentationHeader:The property must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.ClassWithEnumerable.Values")]
[assembly: SuppressMessage("DocumentationHeader", "PropertyDocumentationHeader:The property must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.ClassWithList.Strings")]
[assembly: SuppressMessage("DocumentationHeader", "PropertyDocumentationHeader:The property must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.ClassWithList.Values")]
[assembly: SuppressMessage("DocumentationHeader", "PropertyDocumentationHeader:The property must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.SampleClass.Amount")]
[assembly: SuppressMessage("DocumentationHeader", "PropertyDocumentationHeader:The property must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.SampleClass.Email")]
[assembly: SuppressMessage("DocumentationHeader", "PropertyDocumentationHeader:The property must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.SampleClass2.ConstructorValue")]
[assembly: SuppressMessage("DocumentationHeader", "PropertyDocumentationHeader:The property must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.SampleClass2.Name")]
[assembly: SuppressMessage("DocumentationHeader", "PropertyDocumentationHeader:The property must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.SampleClass2.Timeout")]
[assembly: SuppressMessage("DocumentationHeader", "ClassDocumentationHeader:The class must have a documentation header.", Justification = "<Pending>", Scope = "type", Target = "~T:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests")]
[assembly: SuppressMessage("DocumentationHeader", "ClassDocumentationHeader:The class must have a documentation header.", Justification = "<Pending>", Scope = "type", Target = "~T:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.ClassWithArray")]
[assembly: SuppressMessage("DocumentationHeader", "ClassDocumentationHeader:The class must have a documentation header.", Justification = "<Pending>", Scope = "type", Target = "~T:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.ClassWithEnumerable")]
[assembly: SuppressMessage("DocumentationHeader", "ClassDocumentationHeader:The class must have a documentation header.", Justification = "<Pending>", Scope = "type", Target = "~T:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.ClassWithList")]
[assembly: SuppressMessage("DocumentationHeader", "ClassDocumentationHeader:The class must have a documentation header.", Justification = "<Pending>", Scope = "type", Target = "~T:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.SampleClass")]
[assembly: SuppressMessage("DocumentationHeader", "ClassDocumentationHeader:The class must have a documentation header.", Justification = "<Pending>", Scope = "type", Target = "~T:DisCatSharp.Configuration.Tests.ConfigurationExtensionTests.SampleClass2")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>")]
diff --git a/DisCatSharp.Configuration/ConfigurationExtensions.cs b/DisCatSharp.Configuration/ConfigurationExtensions.cs
index 3e08d68db..ca74208cf 100644
--- a/DisCatSharp.Configuration/ConfigurationExtensions.cs
+++ b/DisCatSharp.Configuration/ConfigurationExtensions.cs
@@ -1,312 +1,312 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Linq;
using DisCatSharp.Configuration.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace DisCatSharp.Configuration;
/// <summary>
/// The configuration extensions.
/// </summary>
internal static class ConfigurationExtensions
{
/// <summary>
/// The factory error message.
/// </summary>
private const string FACTORY_ERROR_MESSAGE = "Require a function which provides a default entity to work with";
/// <summary>
/// The default root lib.
/// </summary>
public const string DEFAULT_ROOT_LIB = "DisCatSharp";
/// <summary>
/// The config suffix.
/// </summary>
private const string CONFIG_SUFFIX = "Configuration";
/// <summary>
/// Easily piece together paths that will work within <see cref="IConfiguration"/>
/// </summary>
/// <param name="config">(not used - only for adding context based functionality)</param>
/// <param name="values">The strings to piece together</param>
/// <returns>Strings joined together via ':'</returns>
public static string ConfigPath(this IConfiguration config, params string[] values) => string.Join(":", values);
/// <summary>
/// Skims over the configuration section and only overrides values that are explicitly defined within the config
/// </summary>
/// <param name="config">Instance of config</param>
/// <param name="section">Section which contains values for <paramref name="config"/></param>
private static void HydrateInstance(ref object config, ConfigSection section)
{
var props = config.GetType().GetProperties();
foreach (var prop in props)
{
// Must have a set method for this to work, otherwise continue on
if (prop.SetMethod == null)
continue;
var entry = section.GetValue(prop.Name);
object? value = null;
if (typeof(string) == prop.PropertyType)
{
// We do NOT want to override value if nothing was provided
if (!string.IsNullOrEmpty(entry))
prop.SetValue(config, entry);
continue;
}
// We need to address collections a bit differently
// They can come in the form of "root:section:name" with a string representation OR
// "root:section:name:0" <--- this is not detectable when checking the above path
if (typeof(IEnumerable).IsAssignableFrom(prop.PropertyType))
{
value = string.IsNullOrEmpty(section.GetValue(prop.Name))
? section.Config
.GetSection(section.GetPath(prop.Name)).Get(prop.PropertyType)
: Newtonsoft.Json.JsonConvert.DeserializeObject(entry, prop.PropertyType);
if (value == null)
continue;
prop.SetValue(config, value);
}
// From this point onward we require the 'entry' value to have something useful
if (string.IsNullOrEmpty(entry))
continue;
try
{
// Primitive types are simple to convert
if (prop.PropertyType.IsPrimitive)
value = Convert.ChangeType(entry, prop.PropertyType);
else
{
// The following types require a different approach
if (prop.PropertyType.IsEnum)
value = Enum.Parse(prop.PropertyType, entry);
else if (typeof(TimeSpan) == prop.PropertyType)
value = TimeSpan.Parse(entry);
else if (typeof(DateTime) == prop.PropertyType)
value = DateTime.Parse(entry);
else if (typeof(DateTimeOffset) == prop.PropertyType)
value = DateTimeOffset.Parse(entry);
}
// Update value within our config instance
prop.SetValue(config, value);
}
catch (Exception ex)
{
Console.Error.WriteLine(
$"Unable to convert value of '{entry}' to type '{prop.PropertyType.Name}' for prop '{prop.Name}' in config '{config.GetType().Name}'\n\t\t{ex.Message}");
}
}
}
/// <summary>
/// Instantiate an entity using <paramref name="factory"/> then walk through the specified <paramref name="section"/>
/// and translate user-defined config values to the instantiated instance from <paramref name="factory"/>
/// </summary>
/// <param name="section">Section containing values for targeted config</param>
/// <param name="factory">Function which generates a default entity</param>
/// <returns>Hydrated instance of an entity which contains user-defined values (if any)</returns>
/// <exception cref="ArgumentNullException">When <paramref name="factory"/> is null</exception>
public static object ExtractConfig(this ConfigSection section, Func<object> factory)
{
if (factory == null)
throw new ArgumentNullException(nameof(factory), FACTORY_ERROR_MESSAGE);
// Create default instance
var config = factory();
HydrateInstance(ref config, section);
return config;
}
/// <summary>
/// Instantiate an entity using <paramref name="factory"/> then walk through the specified <paramref name="sectionName"/>
/// in <paramref name="config"/>. Translate user-defined config values to the instantiated instance from <paramref name="factory"/>
/// </summary>
/// <param name="config">Loaded App Configuration</param>
/// <param name="sectionName">Name of section to load</param>
/// <param name="factory">Function which creates a default entity to work with</param>
/// <param name="rootSectionName">(Optional) Used when section is nested within another. Default value is <see cref="DEFAULT_ROOT_LIB"/></param>
/// <returns>Hydrated instance of an entity which contains user-defined values (if any)</returns>
/// <exception cref="ArgumentNullException">When <paramref name="factory"/> is null</exception>
public static object ExtractConfig(this IConfiguration config, string sectionName, Func<object> factory,
string? rootSectionName = DEFAULT_ROOT_LIB)
{
if (factory == null)
throw new ArgumentNullException(nameof(factory), FACTORY_ERROR_MESSAGE);
// create default instance
var instance = factory();
HydrateInstance(ref instance, new ConfigSection(ref config, sectionName, rootSectionName));
return instance;
}
/// <summary>
/// Instantiate a new instance of <typeparamref name="TConfig"/>, then walk through the specified <paramref name="sectionName"/>
/// in <paramref name="config"/>. Translate user-defined config values to the <typeparamref name="TConfig"/> instance.
/// </summary>
/// <param name="config">Loaded App Configuration</param>
/// <param name="serviceProvider"></param>
/// <param name="sectionName">Name of section to load</param>
/// <param name="rootSectionName">(Optional) Used when section is nested with another. Default value is <see cref="DEFAULT_ROOT_LIB"/></param>
/// <typeparam name="TConfig">Type of instance that <paramref name="sectionName"/> represents</typeparam>
/// <returns>Hydrated instance of <typeparamref name="TConfig"/> which contains the user-defined values (if any).</returns>
public static TConfig ExtractConfig<TConfig>(this IConfiguration config, IServiceProvider serviceProvider, string sectionName, string? rootSectionName = DEFAULT_ROOT_LIB)
where TConfig : new()
{
// Default values should hopefully be provided from the constructor
var configInstance = ActivatorUtilities.CreateInstance(serviceProvider, typeof(TConfig));
HydrateInstance(ref configInstance, new ConfigSection(ref config, sectionName, rootSectionName));
return (TConfig)configInstance;
}
/// <summary>
/// Instantiate a new instance of <typeparamref name="TConfig"/>, then walk through the specified <paramref name="sectionName"/>
/// in <paramref name="config"/>. Translate user-defined config values to the <typeparamref name="TConfig"/> instance.
/// </summary>
/// <param name="config">Loaded App Configuration</param>
/// <param name="sectionName">Name of section to load</param>
/// <param name="rootSectionName">(Optional) Used when section is nested with another. Default value is <see cref="DEFAULT_ROOT_LIB"/></param>
/// <typeparam name="TConfig">Type of instance that <paramref name="sectionName"/> represents</typeparam>
/// <returns>Hydrated instance of <typeparamref name="TConfig"/> which contains the user-defined values (if any).</returns>
public static TConfig ExtractConfig<TConfig>(this IConfiguration config, string sectionName, string? rootSectionName = DEFAULT_ROOT_LIB)
where TConfig : new()
{
// Default values should hopefully be provided from the constructor
object configInstance = new TConfig();
HydrateInstance(ref configInstance, new ConfigSection(ref config, sectionName, rootSectionName));
return (TConfig)configInstance;
}
/// <summary>
/// Determines if <paramref name="config"/> contains a particular section/object (not value)
/// </summary>
/// <remarks>
/// <code>
/// {
/// "Discord": { // this is a section/object
///
/// },
/// "Value": "something" // this is not a section/object
/// }
/// </code>
/// </remarks>
/// <param name="config"></param>
/// <param name="values"></param>
/// <returns>True if section exists, otherwise false</returns>
public static bool HasSection(this IConfiguration config, params string[] values)
{
if (!values.Any())
return false;
if (values.Length == 1)
return config.GetChildren().Any(x => x.Key == values[0]);
if (config.GetChildren().All(x => x.Key != values[0]))
return false;
var current = config.GetSection(values[0]);
for (var i = 1; i < values.Length - 1; i++)
{
if (current.GetChildren().All(x => x.Key != values[i]))
return false;
current = current.GetSection(values[i]);
}
return current.GetChildren().Any(x => x.Key == values[^1]);
}
/// <summary>
/// Instantiates an instance of <see cref="DiscordClient"/>, then consumes any custom
/// configuration from user/developer from <paramref name="config"/>. <br/>
/// View remarks for more info
/// </summary>
/// <remarks>
/// This is an example of how your JSON structure should look if you wish
/// to override one or more of the default values from <see cref="DiscordConfiguration"/>
/// <code>
/// {
/// "DisCatSharp": {
/// "Discord": { }
/// }
/// }
/// </code>
/// <br/>
/// Alternatively, you can use the type name itself
/// <code>
/// {
/// "DisCatSharp": {
/// "DiscordConfiguration": { }
/// }
/// }
/// </code>
/// <code>
/// {
/// "botSectionName": {
/// "DiscordConfiguration": { }
/// }
/// }
/// </code>
/// </remarks>
/// <param name="config"></param>
/// <param name="serviceProvider"></param>
/// <param name="botSectionName"></param>
/// <returns>Instance of <see cref="DiscordClient"/></returns>
public static DiscordClient BuildClient(this IConfiguration config, IServiceProvider serviceProvider,
string botSectionName = DEFAULT_ROOT_LIB)
{
var section = config.HasSection(botSectionName, "Discord")
? "Discord"
: config.HasSection(botSectionName, $"Discord{CONFIG_SUFFIX}")
? $"Discord:{CONFIG_SUFFIX}"
: null;
return string.IsNullOrEmpty(section)
? new DiscordClient(new DiscordConfiguration(serviceProvider))
: new DiscordClient(config.ExtractConfig<DiscordConfiguration>(serviceProvider, section, botSectionName));
}
}
diff --git a/DisCatSharp.Configuration/GlobalSuppressions.cs b/DisCatSharp.Configuration/GlobalSuppressions.cs
index a69c1319e..d84045e36 100644
--- a/DisCatSharp.Configuration/GlobalSuppressions.cs
+++ b/DisCatSharp.Configuration/GlobalSuppressions.cs
@@ -1,25 +1,25 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Configuration.ConfigurationExtensions.ConfigPath(Microsoft.Extensions.Configuration.IConfiguration,System.String[])~System.String")]
diff --git a/DisCatSharp.Configuration/Models/ConfigSection.cs b/DisCatSharp.Configuration/Models/ConfigSection.cs
index 68c11f4e6..1c77b545f 100644
--- a/DisCatSharp.Configuration/Models/ConfigSection.cs
+++ b/DisCatSharp.Configuration/Models/ConfigSection.cs
@@ -1,91 +1,91 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 Microsoft.Extensions.Configuration;
namespace DisCatSharp.Configuration.Models;
/// <summary>
/// Represents an object in <see cref="IConfiguration"/>
/// </summary>
internal readonly struct ConfigSection
{
/// <summary>
/// Key within <see cref="Config"/> which represents an object containing multiple values
/// </summary>
public string SectionName { get; }
/// <summary>
/// Optional used to indicate this section is nested within another
/// </summary>
public string? Root { get; }
/// <summary>
/// Reference to <see cref="IConfiguration"/> used within application
/// </summary>
public IConfiguration Config { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ConfigSection"/> class.
/// </summary>
/// <param name="config">Reference to config</param>
/// <param name="sectionName">Section of interest</param>
/// <param name="rootName">(Optional) Indicates <paramref name="sectionName"/> is nested within this name. Default value is DisCatSharp</param>
public ConfigSection(ref IConfiguration config, string sectionName, string? rootName = "DisCatSharp")
{
this.Config = config;
this.SectionName = sectionName;
this.Root = rootName;
}
/// <summary>
/// Checks if key exists in <see cref="Config"/>
/// </summary>
/// <param name="name">Property / Key to search for in section</param>
/// <returns>True if key exists, otherwise false. Outputs path to config regardless</returns>
public bool ContainsKey(string name)
{
var path = string.IsNullOrEmpty(this.Root)
? this.Config.ConfigPath(this.SectionName, name)
: this.Config.ConfigPath(this.Root, this.SectionName, name);
return !string.IsNullOrEmpty(this.Config[path]);
}
/// <summary>
/// Attempts to get value associated to the config path. <br/> Should be used in unison with <see cref="ContainsKey"/>
/// </summary>
/// <param name="propName">Config path to value</param>
/// <returns>Value found at <paramref name="propName"/></returns>
public string GetValue(string propName)
=> this.Config[this.GetPath(propName)];
/// <summary>
/// Gets the path.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>A string.</returns>
public string GetPath(string value) =>
string.IsNullOrEmpty(this.Root)
? this.Config.ConfigPath(this.SectionName, value)
: this.Config.ConfigPath(this.Root, this.SectionName, value);
}
diff --git a/DisCatSharp.Configuration/Properties/AssemblyProperties.cs b/DisCatSharp.Configuration/Properties/AssemblyProperties.cs
index 5665bd3fb..3f20b1da6 100644
--- a/DisCatSharp.Configuration/Properties/AssemblyProperties.cs
+++ b/DisCatSharp.Configuration/Properties/AssemblyProperties.cs
@@ -1,45 +1,45 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("DisCatSharp.ApplicationCommands")]
[assembly: InternalsVisibleTo("DisCatSharp.CommandsNext")]
[assembly: InternalsVisibleTo("DisCatSharp.Common")]
[assembly: InternalsVisibleTo("DisCatSharp")]
[assembly: InternalsVisibleTo("DisCatSharp.Configuration.Tests")]
[assembly: InternalsVisibleTo("DisCatSharp.Hosting")]
[assembly: InternalsVisibleTo("DisCatSharp.Hosting.DependencyInjection")]
[assembly: InternalsVisibleTo("DisCatSharp.Hosting.Tests")]
[assembly: InternalsVisibleTo("DisCatSharp.Interactivity")]
[assembly: InternalsVisibleTo("DisCatSharp.Lavalink")]
[assembly: InternalsVisibleTo("DisCatSharp.Phabricator")]
[assembly: InternalsVisibleTo("DisCatSharp.Support")]
[assembly: InternalsVisibleTo("DisCatSharp.Test")]
[assembly: InternalsVisibleTo("DisCatSharp.VoiceNext")]
[assembly: InternalsVisibleTo("DisCatSharp.VoiceNext.Natives")]
[assembly: InternalsVisibleTo("Nyaw")]
[assembly: InternalsVisibleTo("DisCatSharp.DevTools")]
[assembly: InternalsVisibleTo("DisCatSharp.DocsGenerator")]
[assembly: InternalsVisibleTo("DisCatSharp.StaffApps")]
[assembly: InternalsVisibleTo("Microsoft.DocAsCode")]
[assembly: InternalsVisibleTo("Microsoft.DocAsCode.Metadata.ManagedReference")]
diff --git a/DisCatSharp.Experimental/DisCatSharp.cs b/DisCatSharp.Experimental/DisCatSharp.cs
index bff0c2995..b711e9c4e 100644
--- a/DisCatSharp.Experimental/DisCatSharp.cs
+++ b/DisCatSharp.Experimental/DisCatSharp.cs
@@ -1,47 +1,38 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
using System.Threading.Tasks;
-using DisCatSharp;
using DisCatSharp.Attributes;
-using DisCatSharp.Entities;
-using DisCatSharp.Enums;
-using DisCatSharp.EventArgs;
-using DisCatSharp.Exceptions;
namespace DisCatSharp.Experimental
{
public static class DisCatSharp
{
[Experimental("This function is being tested and might change at any time.")]
public static async Task<string> GetUsernameAsync(this DiscordClient client, ulong id)
{
var user = await client.ApiClient.GetUserAsync(id);
return user.UsernameWithDiscriminator;
}
}
}
diff --git a/DisCatSharp.Experimental/DiscordApiClientHook.cs b/DisCatSharp.Experimental/DiscordApiClientHook.cs
index 4a5f3ab87..d708329e4 100644
--- a/DisCatSharp.Experimental/DiscordApiClientHook.cs
+++ b/DisCatSharp.Experimental/DiscordApiClientHook.cs
@@ -1,42 +1,36 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
using DisCatSharp.Net;
namespace DisCatSharp.Experimental
{
- internal class DiscordApiClientHook
+ internal class DiscordApiClientHook
{
internal DiscordApiClient ApiClient { get; set; }
public DiscordApiClientHook(DiscordApiClient apiClient)
{
this.ApiClient = apiClient;
}
}
}
diff --git a/DisCatSharp.Experimental/GlobalSuppressions.cs b/DisCatSharp.Experimental/GlobalSuppressions.cs
index a1fe9a6b9..af1c080c9 100644
--- a/DisCatSharp.Experimental/GlobalSuppressions.cs
+++ b/DisCatSharp.Experimental/GlobalSuppressions.cs
@@ -1,7 +1,23 @@
-// This file is used by Code Analysis to maintain SuppressMessage
-// attributes that are applied to this project.
-// Project-level suppressions either have no target or are given
-// a specific target and scoped to a namespace, type, member, etc.
+// This file is part of the DisCatSharp project, based off DSharpPlus.
+//
+// Copyright (c) 2021-2023 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.Diagnostics.CodeAnalysis;
diff --git a/DisCatSharp.Hosting.DependencyInjection/ServiceCollectionExtensions.cs b/DisCatSharp.Hosting.DependencyInjection/ServiceCollectionExtensions.cs
index 060e9dc8b..4bbf5e3b0 100644
--- a/DisCatSharp.Hosting.DependencyInjection/ServiceCollectionExtensions.cs
+++ b/DisCatSharp.Hosting.DependencyInjection/ServiceCollectionExtensions.cs
@@ -1,109 +1,109 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 Microsoft.Extensions.DependencyInjection;
namespace DisCatSharp.Hosting.DependencyInjection;
/// <summary>
/// The service collection extensions.
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Adds your bot as a BackgroundService, registered in Dependency Injection as <typeparamref name="TService"/>
/// </summary>
/// <remarks>
/// <see cref="IDiscordHostedService"/> is scoped to ServiceLifetime.Singleton. <br/>
/// Maps to Implementation of <typeparamref name="TService"/>
/// </remarks>
/// <param name="services"></param>
/// <typeparam name="TService"></typeparam>
/// <returns>Reference to <paramref name="services"/> for chaining purposes</returns>
public static IServiceCollection AddDiscordHostedService<TService>(this IServiceCollection services)
where TService : class, IDiscordHostedService
{
services.AddSingleton<TService>();
services.AddHostedService(provider => provider.GetRequiredService<TService>());
return services;
}
/// <summary>
/// Adds your bot as a BackgroundService, registered in Dependency Injection as <typeparamref name="TService"/>
/// </summary>
/// <remarks>
/// <see cref="IDiscordHostedShardService"/> is scoped to ServiceLifetime.Singleton. <br/>
/// Maps to Implementation of <typeparamref name="TService"/>
/// </remarks>
/// <param name="services"></param>
/// <typeparam name="TService"></typeparam>
/// <returns>Reference to <paramref name="services"/> for chaining purposes</returns>
public static IServiceCollection AddDiscordHostedShardService<TService>(this IServiceCollection services)
where TService : class, IDiscordHostedShardService
{
services.AddSingleton<TService>();
services.AddHostedService(provider => provider.GetRequiredService<TService>());
return services;
}
/// <summary>
/// Add <typeparamref name="TService"/> as a background service which derives from
/// <typeparamref name="TInterface"/> and <see cref="IDiscordHostedService"/>
/// </summary>
/// <remarks>
/// To retrieve your bot via Dependency Injection you can reference it via <typeparamref name="TInterface"/>
/// </remarks>
/// <param name="services"></param>
/// <typeparam name="TInterface">Interface which <typeparamref name="TService"/> inherits from</typeparam>
/// <typeparam name="TService">Your custom bot</typeparam>
/// <returns>Reference to <paramref name="services"/> for chaining purposes</returns>
public static IServiceCollection AddDiscordHostedService<TInterface, TService>(this IServiceCollection services)
where TInterface : class, IDiscordHostedService
where TService : class, TInterface, IDiscordHostedService
{
services.AddSingleton<TInterface, TService>();
services.AddHostedService(provider => provider.GetRequiredService<TInterface>());
return services;
}
/// <summary>
/// Add <typeparamref name="TService"/> as a background service which derives from
/// <typeparamref name="TInterface"/> and <see cref="IDiscordHostedShardService"/>
/// </summary>
/// <remarks>
/// To retrieve your bot via Dependency Injection you can reference it via <typeparamref name="TInterface"/>
/// </remarks>
/// <param name="services"></param>
/// <typeparam name="TInterface">Interface which <typeparamref name="TService"/> inherits from</typeparam>
/// <typeparam name="TService">Your custom bot</typeparam>
/// <returns>Reference to <paramref name="services"/> for chaining purposes</returns>
public static IServiceCollection AddDiscordHostedShardService<TInterface, TService>(
this IServiceCollection services)
where TInterface : class, IDiscordHostedShardService
where TService : class, TInterface, IDiscordHostedShardService
{
services.AddSingleton<TInterface, TService>();
services.AddHostedService(provider => provider.GetRequiredService<TInterface>());
return services;
}
}
diff --git a/DisCatSharp.Hosting.Tests/ExtensionTests.cs b/DisCatSharp.Hosting.Tests/ExtensionTests.cs
index 6ab7c7e8e..393e545ea 100644
--- a/DisCatSharp.Hosting.Tests/ExtensionTests.cs
+++ b/DisCatSharp.Hosting.Tests/ExtensionTests.cs
@@ -1,121 +1,121 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.Interactivity;
using DisCatSharp.Lavalink;
using Microsoft.Extensions.Configuration;
using Xunit;
namespace DisCatSharp.Hosting.Tests;
public class HostExtensionTests
{
#region Reference to external assemblies - required to ensure they're loaded
#pragma warning disable 414
private InteractivityConfiguration? _interactivityConfig = null;
private LavalinkConfiguration? _lavalinkConfig = null;
private DiscordConfiguration? _discordConfig = null;
#pragma warning restore 414
#endregion
private Dictionary<string, string> DefaultDiscord() =>
new()
{
{ "DisCatSharp:Discord:Token", "1234567890" },
{ "DisCatSharp:Discord:TokenType", "Bot" },
{ "DisCatSharp:Discord:MinimumLogLevel", "Information" },
{ "DisCatSharp:Discord:UseRelativeRateLimit", "true" },
{ "DisCatSharp:Discord:LogTimestampFormat", "yyyy-MM-dd HH:mm:ss zzz" },
{ "DisCatSharp:Discord:LargeThreshold", "250" },
{ "DisCatSharp:Discord:AutoReconnect", "true" },
{ "DisCatSharp:Discord:ShardId", "123123" },
{ "DisCatSharp:Discord:GatewayCompressionLevel", "Stream" },
{ "DisCatSharp:Discord:MessageCacheSize", "1024" },
{ "DisCatSharp:Discord:HttpTimeout", "00:00:20" },
{ "DisCatSharp:Discord:ReconnectIndefinitely", "false" },
{ "DisCatSharp:Discord:AlwaysCacheMembers", "true" },
{ "DisCatSharp:Discord:DiscordIntents", "AllUnprivileged" },
{ "DisCatSharp:Discord:MobileStatus", "false" },
{ "DisCatSharp:Discord:UseCanary", "false" },
{ "DisCatSharp:Discord:AutoRefreshChannelCache", "false" },
{ "DisCatSharp:Discord:Intents", "AllUnprivileged" }
};
public IConfiguration DiscordInteractivityConfiguration() => new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>(this.DefaultDiscord())
{
{"DisCatSharp:Using", "[\"DisCatSharp.Interactivity\"]"} // this should be enough to automatically add the extension
})
.Build();
public IConfiguration DiscordOnlyConfiguration() => new ConfigurationBuilder()
.AddInMemoryCollection(this.DefaultDiscord())
.Build();
public IConfiguration DiscordInteractivityAndLavaLinkConfiguration() => new ConfigurationBuilder()
.AddJsonFile("interactivity-lavalink.json")
.Build();
[Fact]
public void DiscoverExtensions_Interactivity()
{
var source = this.DiscordInteractivityConfiguration();
var discovered = source.FindImplementedExtensions();
// Remember that DiscordConfiguration does not have an implementation type which is assignable to BaseExtension
Assert.Single(discovered);
var item = discovered.First();
Assert.Equal(typeof(InteractivityConfiguration), item.Value.ConfigType);
Assert.Equal(typeof(InteractivityExtension), item.Value.ImplementationType);
Assert.Equal("InteractivityExtension", item.Key);
}
[Fact]
public void DiscoverExtensions_InteractivityAndLavaLink()
{
var source = this.DiscordInteractivityAndLavaLinkConfiguration();
var discovered = source.FindImplementedExtensions();
Assert.Equal(2, discovered.Count);
var first = discovered.First();
var last = discovered.Last();
Assert.Equal(typeof(InteractivityConfiguration), first.Value.ConfigType);
Assert.Equal(typeof(InteractivityExtension), first.Value.ImplementationType);
Assert.True("InteractivityExtension".Equals(first.Key, StringComparison.OrdinalIgnoreCase));
Assert.Equal(typeof(LavalinkConfiguration), last.Value.ConfigType);
Assert.Equal(typeof(LavalinkExtension), last.Value.ImplementationType);
Assert.True("LavalinkExtension".Equals(last.Key, StringComparison.OrdinalIgnoreCase));
}
}
diff --git a/DisCatSharp.Hosting.Tests/GlobalSuppressions.cs b/DisCatSharp.Hosting.Tests/GlobalSuppressions.cs
index aee10d128..6464677d1 100644
--- a/DisCatSharp.Hosting.Tests/GlobalSuppressions.cs
+++ b/DisCatSharp.Hosting.Tests/GlobalSuppressions.cs
@@ -1,42 +1,42 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("DocumentationHeader", "ClassDocumentationHeader:The class must have a documentation header.", Justification = "<Pending>")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>")]
[assembly: SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "<Pending>", Scope = "member", Target = "~F:DisCatSharp.Hosting.Tests.HostExtensionTests._discordConfig")]
[assembly: SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "<Pending>", Scope = "member", Target = "~F:DisCatSharp.Hosting.Tests.HostExtensionTests._discordConfig")]
[assembly: SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "<Pending>", Scope = "member", Target = "~F:DisCatSharp.Hosting.Tests.HostExtensionTests._interactivityConfig")]
[assembly: SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "<Pending>", Scope = "member", Target = "~F:DisCatSharp.Hosting.Tests.HostExtensionTests._interactivityConfig")]
[assembly: SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "<Pending>", Scope = "member", Target = "~F:DisCatSharp.Hosting.Tests.HostExtensionTests._lavalinkConfig")]
[assembly: SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "<Pending>", Scope = "member", Target = "~F:DisCatSharp.Hosting.Tests.HostExtensionTests._lavalinkConfig")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Hosting.Tests.HostExtensionTests.DefaultDiscord~System.Collections.Generic.Dictionary{System.String,System.String}")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Hosting.Tests.HostExtensionTests.DiscordInteractivityAndLavaLinkConfiguration~Microsoft.Extensions.Configuration.IConfiguration")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Hosting.Tests.HostTests.Create(System.Collections.Generic.Dictionary{System.String,System.String})~Microsoft.Extensions.Hosting.IHostBuilder")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Hosting.Tests.HostTests.DefaultDiscord~System.Collections.Generic.Dictionary{System.String,System.String}")]
[assembly: SuppressMessage("DocumentationHeader", "InterfaceDocumentationHeader:The interface must have a documentation header.", Justification = "<Pending>", Scope = "type", Target = "~T:DisCatSharp.Hosting.Tests.IBotTwoService")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Hosting.Tests.HostTests.Create(System.String)~Microsoft.Extensions.Hosting.IHostBuilder")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Hosting.Tests.HostTests.Create``2(System.String)~Microsoft.Extensions.Hosting.IHostBuilder")]
[assembly: SuppressMessage("DocumentationHeader", "ConstructorDocumentationHeader:The constructor must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Hosting.Tests.Bot.#ctor(Microsoft.Extensions.Configuration.IConfiguration,Microsoft.Extensions.Logging.ILogger{DisCatSharp.Hosting.Tests.Bot},System.IServiceProvider,Microsoft.Extensions.Hosting.IHostApplicationLifetime)")]
[assembly: SuppressMessage("DocumentationHeader", "ConstructorDocumentationHeader:The constructor must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Hosting.Tests.BotTwoService.#ctor(Microsoft.Extensions.Configuration.IConfiguration,Microsoft.Extensions.Logging.ILogger{DisCatSharp.Hosting.Tests.BotTwoService},System.IServiceProvider,Microsoft.Extensions.Hosting.IHostApplicationLifetime)")]
[assembly: SuppressMessage("DocumentationHeader", "ConstructorDocumentationHeader:The constructor must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Hosting.Tests.MyCustomBot.#ctor(Microsoft.Extensions.Configuration.IConfiguration,Microsoft.Extensions.Logging.ILogger{DisCatSharp.Hosting.Tests.MyCustomBot},System.IServiceProvider,Microsoft.Extensions.Hosting.IHostApplicationLifetime)")]
diff --git a/DisCatSharp.Hosting.Tests/HostTests.cs b/DisCatSharp.Hosting.Tests/HostTests.cs
index 68f7ff134..1f2582415 100644
--- a/DisCatSharp.Hosting.Tests/HostTests.cs
+++ b/DisCatSharp.Hosting.Tests/HostTests.cs
@@ -1,266 +1,266 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using DisCatSharp.Interactivity;
using DisCatSharp.Lavalink;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Xunit;
namespace DisCatSharp.Hosting.Tests;
public sealed class Bot : DiscordHostedService
{
public Bot(IConfiguration config, ILogger<Bot> logger, IServiceProvider provider, IHostApplicationLifetime lifetime) : base(config, logger, provider, lifetime)
{
this.ConfigureAsync().GetAwaiter().GetResult();
this.ConfigureExtensionsAsync().GetAwaiter().GetResult();
}
}
public sealed class MyCustomBot : DiscordHostedService
{
public MyCustomBot(IConfiguration config, ILogger<MyCustomBot> logger, IServiceProvider provider, IHostApplicationLifetime lifetime) : base(config, logger, provider, lifetime, "MyCustomBot")
{
this.ConfigureAsync().GetAwaiter().GetResult();
this.ConfigureExtensionsAsync().GetAwaiter().GetResult();
}
}
public interface IBotTwoService : IDiscordHostedService
{
string GiveMeAResponse();
}
public sealed class BotTwoService : DiscordHostedService, IBotTwoService
{
public BotTwoService(IConfiguration config, ILogger<BotTwoService> logger, IServiceProvider provider, IHostApplicationLifetime lifetime) : base(config, logger, provider, lifetime, "BotTwo")
{
this.ConfigureAsync().GetAwaiter().GetResult();
this.ConfigureExtensionsAsync().GetAwaiter().GetResult();
}
public string GiveMeAResponse() => "I'm working";
}
public class HostTests
{
private Dictionary<string, string> DefaultDiscord() =>
new()
{
{ "DisCatSharp:Discord:Token", "1234567890" },
{ "DisCatSharp:Discord:TokenType", "Bot" },
{ "DisCatSharp:Discord:MinimumLogLevel", "Information" },
{ "DisCatSharp:Discord:UseRelativeRateLimit", "true" },
{ "DisCatSharp:Discord:LogTimestampFormat", "yyyy-MM-dd HH:mm:ss zzz" },
{ "DisCatSharp:Discord:LargeThreshold", "250" },
{ "DisCatSharp:Discord:AutoReconnect", "true" },
{ "DisCatSharp:Discord:ShardId", "123123" },
{ "DisCatSharp:Discord:GatewayCompressionLevel", "Stream" },
{ "DisCatSharp:Discord:MessageCacheSize", "1024" },
{ "DisCatSharp:Discord:HttpTimeout", "00:00:20" },
{ "DisCatSharp:Discord:ReconnectIndefinitely", "false" },
{ "DisCatSharp:Discord:AlwaysCacheMembers", "true" },
{ "DisCatSharp:Discord:DiscordIntents", "AllUnprivileged" },
{ "DisCatSharp:Discord:MobileStatus", "false" },
{ "DisCatSharp:Discord:UseCanary", "false" },
{ "DisCatSharp:Discord:AutoRefreshChannelCache", "false" },
{ "DisCatSharp:Discord:Intents", "AllUnprivileged" }
};
public Dictionary<string, string> DiscordInteractivity() => new(this.DefaultDiscord())
{
{ "DisCatSharp:Using", "[\"DisCatSharp.Interactivity\"]" },
};
public Dictionary<string, string> DiscordInteractivityAndLavalink() => new(this.DefaultDiscord())
{
{ "DisCatSharp:Using", "[\"DisCatSharp.Interactivity\", \"DisCatSharp.Lavalink\"]" },
};
IHostBuilder Create(Dictionary<string, string> configValues) =>
Host.CreateDefaultBuilder()
.ConfigureServices(services => services.AddSingleton<IDiscordHostedService, Bot>())
.ConfigureHostConfiguration(builder => builder.AddInMemoryCollection(configValues));
IHostBuilder Create(string filename) =>
Host.CreateDefaultBuilder()
.ConfigureServices(services => services.AddSingleton<IDiscordHostedService, MyCustomBot>())
.ConfigureHostConfiguration(builder => builder.AddJsonFile(filename));
IHostBuilder Create<TInterface, TBot>(string filename)
where TInterface : class, IDiscordHostedService
where TBot : class, TInterface, IDiscordHostedService =>
Host.CreateDefaultBuilder()
.ConfigureServices(services => services.AddSingleton<TInterface, TBot>())
.ConfigureHostConfiguration(builder => builder.AddJsonFile(filename));
[Fact]
public void TestBotCustomInterface()
{
IHost? host = null;
try
{
host = this.Create<IBotTwoService, BotTwoService>("BotTwo.json").Build();
var service = host.Services.GetRequiredService<IBotTwoService>();
Assert.NotNull(service);
var response = service.GiveMeAResponse();
Assert.Equal("I'm working", response);
}
finally
{
host?.Dispose();
}
}
[Fact]
public void TestDifferentSection_InteractivityOnly()
{
IHost? host = null;
try
{
host = this.Create("interactivity-different-section.json").Build();
var service = host.Services.GetRequiredService<IDiscordHostedService>();
Assert.NotNull(service);
Assert.NotNull(service.Client);
Assert.Null(service.Client.GetExtension<LavalinkExtension>());
var intents = DiscordIntents.GuildEmojisAndStickers | DiscordIntents.GuildMembers |
DiscordIntents.Guilds;
Assert.Equal(intents, service.Client.Intents);
var interactivity = service.Client.GetExtension<InteractivityExtension>();
Assert.NotNull(interactivity);
Assert.NotNull(host.Services);
Assert.NotNull(service.Client.ServiceProvider);
}
finally
{
host?.Dispose();
}
}
[Fact]
public void TestDifferentSection_LavalinkOnly()
{
IHost? host = null;
try
{
host = this.Create("lavalink-different-section.json").Build();
var service = host.Services.GetRequiredService<IDiscordHostedService>();
Assert.NotNull(service);
Assert.NotNull(service.Client);
Assert.NotNull(service.Client.GetExtension<LavalinkExtension>());
Assert.Null(service.Client.GetExtension<InteractivityExtension>());
var intents = DiscordIntents.Guilds;
Assert.Equal(intents, service.Client.Intents);
Assert.NotNull(service.Client.ServiceProvider);
}
finally
{
host?.Dispose();
}
}
[Fact]
public void TestNoExtensions()
{
IHost? host = null;
try
{
host = this.Create(this.DefaultDiscord()).Build();
var service = host.Services.GetRequiredService<IDiscordHostedService>();
Assert.NotNull(service);
Assert.NotNull(service.Client);
Assert.NotNull(service.Client.ServiceProvider);
}
finally
{
host?.Dispose();
}
}
[Fact]
public void TestInteractivityExtension()
{
IHost? host = null;
try
{
host = this.Create(this.DiscordInteractivity()).Build();
var service = host.Services.GetRequiredService<IDiscordHostedService>();
Assert.NotNull(service);
Assert.NotNull(service.Client);
Assert.NotNull(service.Client.GetExtension<InteractivityExtension>());
Assert.NotNull(service.Client.ServiceProvider);
}
finally
{
host?.Dispose();
}
}
[Fact]
public void TestInteractivityLavalinkExtensions()
{
IHost? host = null;
try
{
host = this.Create(this.DiscordInteractivityAndLavalink()).Build();
var service = host.Services.GetRequiredService<IDiscordHostedService>();
Assert.NotNull(service);
Assert.NotNull(service.Client);
Assert.NotNull(service.Client.GetExtension<InteractivityExtension>());
Assert.NotNull(service.Client.GetExtension<LavalinkExtension>());
Assert.NotNull(service.Client.ServiceProvider);
}
finally
{
host?.Dispose();
}
}
}
diff --git a/DisCatSharp.Hosting/BaseHostedService.cs b/DisCatSharp.Hosting/BaseHostedService.cs
index f176e68f7..8c165f6fb 100644
--- a/DisCatSharp.Hosting/BaseHostedService.cs
+++ b/DisCatSharp.Hosting/BaseHostedService.cs
@@ -1,205 +1,205 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using DisCatSharp.Configuration;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace DisCatSharp.Hosting;
/// <summary>
/// Contains the common logic between having a <see cref="DiscordClient"/> or
/// <see cref="DiscordShardedClient"/> as a Hosted Service
/// </summary>
public abstract class BaseHostedService : BackgroundService
{
protected readonly ILogger<BaseHostedService> Logger;
protected readonly IHostApplicationLifetime ApplicationLifetime;
protected readonly IConfiguration Configuration;
protected readonly IServiceProvider ServiceProvider;
protected readonly string BotSection;
/// <summary>
/// Initializes a new instance of the <see cref="BaseHostedService"/> class.
/// </summary>
/// <param name="config">The config.</param>
/// <param name="logger">The logger.</param>
/// <param name="serviceProvider">The service provider.</param>
/// <param name="applicationLifetime">The application lifetime.</param>
/// <param name="configBotSection">The config bot section.</param>
internal BaseHostedService(IConfiguration config,
ILogger<BaseHostedService> logger,
IServiceProvider serviceProvider,
IHostApplicationLifetime applicationLifetime,
string configBotSection = DisCatSharp.Configuration.ConfigurationExtensions.DEFAULT_ROOT_LIB)
{
this.Configuration = config;
this.Logger = logger;
this.ApplicationLifetime = applicationLifetime;
this.ServiceProvider = serviceProvider;
this.BotSection = configBotSection;
}
/// <summary>
/// When the bot(s) fail to start, this method will be invoked. (Default behavior is to shutdown)
/// </summary>
/// <param name="ex">The exception/reason for not starting</param>
protected virtual void OnInitializationError(Exception ex) => this.ApplicationLifetime.StopApplication();
/// <summary>
/// Connect your client(s) to Discord
/// </summary>
/// <returns>Task</returns>
protected abstract Task ConnectAsync();
/// <summary>
/// Dynamically load extensions by using <see cref="Configuration"/> and
/// <see cref="ServiceProvider"/>
/// </summary>
/// <param name="client">Client to add extension method(s) to</param>
/// <returns>Task</returns>
protected Task InitializeExtensions(DiscordClient client)
{
var typeMap = this.Configuration.FindImplementedExtensions(this.BotSection);
this.Logger.LogDebug($"Found the following config types: {string.Join("\n\t", typeMap.Keys)}");
foreach (var typePair in typeMap)
try
{
/*
If section is null --> utilize the default constructor
This means the extension was explicitly added in the 'Using' array,
but user did not wish to override any value(s) in the extension's config
*/
var configInstance = typePair.Value.Section.HasValue
? typePair.Value.Section.Value.ExtractConfig(() =>
ActivatorUtilities.CreateInstance(this.ServiceProvider, typePair.Value.ConfigType))
: ActivatorUtilities.CreateInstance(this.ServiceProvider, typePair.Value.ConfigType);
/*
Explanation for bindings
Internal Constructors --> NonPublic
Public Constructors --> Public
Constructors --> Instance
*/
var flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;
var ctors = typePair.Value.ImplementationType.GetConstructors(flags);
var instance = ctors.Any(x => x.GetParameters().Length == 1 && x.GetParameters().First().ParameterType == typePair.Value.ConfigType)
? Activator.CreateInstance(typePair.Value.ImplementationType, flags, null,
new[] { configInstance }, null)
: Activator.CreateInstance(typePair.Value.ImplementationType, true);
/*
Certain extensions do not require a configuration argument
Those who do -- pass config instance in,
Those who don't -- simply instantiate
ActivatorUtilities requires a public constructor, anything with internal breaks
*/
if (instance == null)
{
this.Logger.LogError($"Unable to instantiate '{typePair.Value.ImplementationType.Name}'");
continue;
}
// Add an easy reference to our extensions for later use
client.AddExtension((BaseExtension)instance);
}
catch (Exception ex)
{
this.Logger.LogError($"Unable to register '{typePair.Value.ImplementationType.Name}': \n\t{ex.Message}");
this.OnInitializationError(ex);
}
return Task.CompletedTask;
}
/// <summary>
/// Configure / Initialize the <see cref="DiscordClient"/> or
/// <see cref="DiscordShardedClient"/>
/// </summary>
/// <returns></returns>
protected abstract Task ConfigureAsync();
/// <summary>
/// Configure the extensions for your <see cref="DiscordShardedClient"/> or
/// <see cref="DiscordClient"/>
/// </summary>
/// <returns></returns>
protected abstract Task ConfigureExtensionsAsync();
/// <summary>
/// Runs just prior to <see cref="ConnectAsync"/>.
/// </summary>
/// <returns></returns>
protected virtual Task PreConnectAsync() => Task.CompletedTask;
/// <summary>
/// Runs immediately after <see cref="ConnectAsync"/>.
/// </summary>
/// <returns>Task</returns>
protected virtual Task PostConnectAsync() => Task.CompletedTask;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
await this.ConfigureAsync();
await this.PreConnectAsync();
await this.ConnectAsync();
await this.ConfigureExtensionsAsync();
await this.PostConnectAsync();
}
catch (Exception ex)
{
/*
* Anything before DOTNET 6 will
* fail silently despite throwing an exception in this method
* So to overcome this obstacle we need to log what happens and
* manually exit
*/
this.Logger.LogError($"Was unable to start {this.GetType().Name} Bot as a Hosted Service");
// Power given to developer for handling exception
this.OnInitializationError(ex);
}
// Wait indefinitely -- but use stopping token so we can properly cancel if needed
await Task.Delay(-1, stoppingToken);
}
}
diff --git a/DisCatSharp.Hosting/ConfigurationExtensions.cs b/DisCatSharp.Hosting/ConfigurationExtensions.cs
index 5b836ea7c..4a91e1a02 100644
--- a/DisCatSharp.Hosting/ConfigurationExtensions.cs
+++ b/DisCatSharp.Hosting/ConfigurationExtensions.cs
@@ -1,199 +1,199 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.Configuration;
using DisCatSharp.Configuration.Models;
using Microsoft.Extensions.Configuration;
namespace DisCatSharp.Hosting;
internal struct ExtensionConfigResult
{
/// <summary>
/// Gets or sets the section.
/// </summary>
public ConfigSection? Section { get; set; }
/// <summary>
/// Gets or sets the config type.
/// </summary>
public Type ConfigType { get; set; }
/// <summary>
/// Gets or sets the implementation type.
/// </summary>
public Type ImplementationType { get; set; }
}
/// <summary>
/// The configuration extensions.
/// </summary>
internal static class ConfigurationExtensions
{
/// <summary>
/// Find assemblies that match the names provided via <paramref name="names"/>.
/// </summary>
/// <remarks>
/// In some cases the assembly the user is after could be used in the application but
/// not appear within the <see cref="AppDomain"/>. <br/>
/// The workaround for this is to check the assemblies in the <see cref="AppDomain"/>, as well as referenced
/// assemblies. If the targeted assembly is a reference, we need to load it into our workspace to get more info.
/// </remarks>
/// <param name="names">Names of assemblies to look for</param>
/// <returns>Assemblies which meet the given names. No duplicates</returns>
public static List<Assembly> FindAssemblies(IEnumerable<string>? names)
{
/*
There is a possibility that an assembly can be referenced in multiple assemblies.
To alleviate duplicates we need to shrink our queue as we find things
*/
List<Assembly> results = new();
if (names is null)
return results;
var queue = names.ToList();
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (!queue.Any())
break;
var loadedAssemblyName = assembly.GetName().Name;
// Kinda need the name to do our thing
if (loadedAssemblyName == null)
continue;
// Is this something we're looking for?
if (queue.Remove(loadedAssemblyName))
{
results.Add(assembly);
}
// Time to check if one of the referenced assemblies is something we're looking for
foreach (var referencedAssembly in assembly.GetReferencedAssemblies()
.Where(x => x.Name != null && queue.Contains(x.Name)))
try
{
// Must load the assembly into our workspace so we can do stuff with it later
results.Add(Assembly.Load(referencedAssembly));
#pragma warning disable 8604
queue.Remove(referencedAssembly.Name);
#pragma warning restore 8604
}
catch (Exception ex)
{
Console.Error.WriteLine($"Unable to load referenced assembly: '{referencedAssembly.Name}' \n\t{ex.Message}");
}
}
return results;
}
/// <summary>
/// Easily identify which configuration types have been added to the <paramref name="configuration"/> <br/>
/// This way we can dynamically load extensions without explicitly doing so
/// </summary>
/// <param name="configuration"></param>
/// <param name="rootName"></param>
/// <returns>Dictionary where Key -> Name of implemented type<br/>Value -> <see cref="ExtensionConfigResult"/></returns>
public static Dictionary<string, ExtensionConfigResult> FindImplementedExtensions(this IConfiguration configuration,
string rootName = Configuration.ConfigurationExtensions.DEFAULT_ROOT_LIB)
{
if (string.IsNullOrEmpty(rootName))
throw new ArgumentNullException(nameof(rootName), "Root name must be provided");
Dictionary<string, ExtensionConfigResult> results = new();
string[]? assemblyNames;
// Has the user defined a using section within the root name?
if (!configuration.HasSection(rootName, "Using"))
return results;
/*
There are 2 ways a user could list which assemblies are used
"Using": "[\"Assembly.Name\"]"
"Using": ["Assembly.Name"]
JSON or as Text.
*/
assemblyNames = string.IsNullOrEmpty(configuration[configuration.ConfigPath(rootName, "Using")])
? configuration.GetSection(configuration.ConfigPath(rootName, "Using")).Get<string[]>()
: Newtonsoft.Json.JsonConvert.DeserializeObject<string[]>(
configuration[configuration.ConfigPath(rootName, "Using")]);
#pragma warning disable 8604
foreach (var assembly in FindAssemblies(assemblyNames.Select(x => x.StartsWith(Constants.LibName) ? x : $"{Constants.LibName}.{x}")))
{
ExtensionConfigResult result = new();
foreach (var type in assembly.ExportedTypes
.Where(x => x.Name.EndsWith(Constants.ConfigSuffix) && !x.IsAbstract && !x.IsInterface))
{
string sectionName = type.Name;
string prefix = type.Name.Replace(Constants.ConfigSuffix, "");
result.ConfigType = type;
// Does a section exist with the classname? (DiscordConfiguration - for instance)
if (configuration.HasSection(rootName, sectionName))
result.Section = new ConfigSection(ref configuration, type.Name, rootName);
// Does a section exist with the classname minus Configuration? (Discord - for Instance)
else if (configuration.HasSection(rootName, prefix))
result.Section = new ConfigSection(ref configuration, prefix, rootName);
// IF THE SECTION IS NOT PROVIDED --> WE WILL USE DEFAULT CONFIG IMPLEMENTATION
/*
Now we need to find the type which should consume our config
In the event a user has some "fluff" between prefix and suffix we'll
just check for beginning and ending values.
Type should not be an interface or abstract, should also be assignable to BaseExtension
*/
var implementationType = assembly.ExportedTypes.FirstOrDefault(x =>
!x.IsAbstract && !x.IsInterface && x.Name.StartsWith(prefix) &&
x.Name.EndsWith(Constants.ExtensionSuffix) && x.IsAssignableTo(typeof(BaseExtension)));
// If the implementation type was found we can add it to our result set
if (implementationType != null)
{
result.ImplementationType = implementationType;
results.Add(implementationType.Name, result);
}
}
}
#pragma warning restore 8604
return results;
}
}
diff --git a/DisCatSharp.Hosting/Constants.cs b/DisCatSharp.Hosting/Constants.cs
index 8a7ce0133..174e5dd2d 100644
--- a/DisCatSharp.Hosting/Constants.cs
+++ b/DisCatSharp.Hosting/Constants.cs
@@ -1,42 +1,42 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Hosting;
/// <summary>
/// The constants.
/// </summary>
internal static class Constants
{
/// <summary>
/// Gets the lib name.
/// </summary>
public static string LibName => Configuration.ConfigurationExtensions.DEFAULT_ROOT_LIB;
/// <summary>
/// Gets the config suffix.
/// </summary>
public static string ConfigSuffix => "Configuration";
/// <summary>
/// Gets the extension suffix.
/// </summary>
public static string ExtensionSuffix => "Extension";
}
diff --git a/DisCatSharp.Hosting/DiscordHostedService.cs b/DisCatSharp.Hosting/DiscordHostedService.cs
index c9397851d..191bbe170 100644
--- a/DisCatSharp.Hosting/DiscordHostedService.cs
+++ b/DisCatSharp.Hosting/DiscordHostedService.cs
@@ -1,83 +1,83 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Threading.Tasks;
using DisCatSharp.Configuration;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace DisCatSharp.Hosting;
/// <summary>
/// Simple implementation for <see cref="DiscordClient"/> to work as a <see cref="Microsoft.Extensions.Hosting.BackgroundService"/>
/// </summary>
public abstract class DiscordHostedService : BaseHostedService, IDiscordHostedService
{
/// <inheritdoc/>
public DiscordClient Client { get; protected set; }
#pragma warning disable 8618
/// <summary>
/// Initializes a new instance of the <see cref="DiscordHostedService"/> class.
/// </summary>
/// <param name="config">IConfiguration provided via Dependency Injection. Aggregate method to access configuration files </param>
/// <param name="logger">An ILogger to work with, provided via Dependency Injection</param>
/// <param name="serviceProvider">ServiceProvider reference which contains all items currently registered for Dependency Injection</param>
/// <param name="applicationLifetime">Contains the appropriate methods for disposing / stopping BackgroundServices during runtime</param>
/// <param name="configBotSection">The name of the JSON/Config Key which contains the configuration for this Discord Service</param>
protected DiscordHostedService(IConfiguration config,
ILogger<DiscordHostedService> logger,
IServiceProvider serviceProvider,
IHostApplicationLifetime applicationLifetime,
string configBotSection = DisCatSharp.Configuration.ConfigurationExtensions.DEFAULT_ROOT_LIB)
: base(config, logger, serviceProvider, applicationLifetime, configBotSection)
{
}
#pragma warning restore 8618
protected override Task ConfigureAsync()
{
try
{
this.Client = this.Configuration.BuildClient(this.ServiceProvider, this.BotSection);
}
catch (Exception ex)
{
this.Logger.LogError($"Was unable to build {nameof(DiscordClient)} for {this.GetType().Name}");
this.OnInitializationError(ex);
}
return Task.CompletedTask;
}
protected sealed override async Task ConnectAsync() => await this.Client.ConnectAsync();
protected override Task ConfigureExtensionsAsync()
{
this.InitializeExtensions(this.Client);
return Task.CompletedTask;
}
}
diff --git a/DisCatSharp.Hosting/DiscordSharedHostedService.cs b/DisCatSharp.Hosting/DiscordSharedHostedService.cs
index ef4ec2708..730944cd7 100644
--- a/DisCatSharp.Hosting/DiscordSharedHostedService.cs
+++ b/DisCatSharp.Hosting/DiscordSharedHostedService.cs
@@ -1,88 +1,88 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Threading.Tasks;
using DisCatSharp.Configuration;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace DisCatSharp.Hosting;
/// <summary>
/// Simple Implementation for <see cref="DiscordShardedClient"/> to work as a <see cref="Microsoft.Extensions.Hosting.BackgroundService"/>
/// </summary>
public abstract class DiscordShardedHostedService : BaseHostedService, IDiscordHostedShardService
{
public DiscordShardedClient ShardedClient { get; protected set; }
#pragma warning disable 8618
/// <summary>
/// Initializes a new instance of the <see cref="DiscordShardedHostedService"/> class.
/// </summary>
/// <param name="config">The config.</param>
/// <param name="logger">The logger.</param>
/// <param name="serviceProvider">The service provider.</param>
/// <param name="applicationLifetime">The application lifetime.</param>
/// <param name="configBotSection">The config bot section.</param>
protected DiscordShardedHostedService(IConfiguration config,
ILogger<DiscordShardedHostedService> logger,
IServiceProvider serviceProvider,
IHostApplicationLifetime applicationLifetime,
string configBotSection = DisCatSharp.Configuration.ConfigurationExtensions.DEFAULT_ROOT_LIB)
: base(config, logger, serviceProvider, applicationLifetime, configBotSection)
{
}
#pragma warning restore 8618
protected override Task ConfigureAsync()
{
try
{
var config = this.Configuration.ExtractConfig<DiscordConfiguration>(this.ServiceProvider, "Discord", this.BotSection);
this.ShardedClient = new DiscordShardedClient(config);
}
catch (Exception ex)
{
this.Logger.LogError($"Was unable to build {nameof(DiscordShardedClient)} for {this.GetType().Name}");
this.OnInitializationError(ex);
}
return Task.CompletedTask;
}
protected sealed override async Task ConnectAsync() => await this.ShardedClient.StartAsync();
protected override Task ConfigureExtensionsAsync()
{
foreach (var client in this.ShardedClient.ShardClients.Values)
{
this.InitializeExtensions(client);
}
return Task.CompletedTask;
}
}
diff --git a/DisCatSharp.Hosting/GlobalSuppressions.cs b/DisCatSharp.Hosting/GlobalSuppressions.cs
index 6770ed025..0cf79e459 100644
--- a/DisCatSharp.Hosting/GlobalSuppressions.cs
+++ b/DisCatSharp.Hosting/GlobalSuppressions.cs
@@ -1,37 +1,37 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Hosting.BaseHostedService.ExecuteAsync(System.Threading.CancellationToken)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Hosting.DiscordHostedService.ConfigureAsync~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Hosting.DiscordHostedService.ConfigureExtensionsAsync~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Hosting.DiscordHostedService.ConnectAsync~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Hosting.DiscordShardedHostedService.ConfigureAsync~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Hosting.DiscordShardedHostedService.ConfigureExtensionsAsync~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("DocumentationHeader", "MethodDocumentationHeader:The method must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Hosting.DiscordShardedHostedService.ConnectAsync~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("DocumentationHeader", "PropertyDocumentationHeader:The property must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.Hosting.DiscordShardedHostedService.ShardedClient")]
[assembly: SuppressMessage("DocumentationHeader", "PropertyDocumentationHeader:The property must have a documentation header.", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.Hosting.IDiscordHostedShardService.ShardedClient")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Hosting.BaseHostedService.ExecuteAsync(System.Threading.CancellationToken)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Hosting.BaseHostedService.InitializeExtensions(DisCatSharp.DiscordClient)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Hosting.DiscordHostedService.ConfigureAsync~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Hosting.DiscordShardedHostedService.ConfigureAsync~System.Threading.Tasks.Task")]
diff --git a/DisCatSharp.Hosting/IDiscordHostedService.cs b/DisCatSharp.Hosting/IDiscordHostedService.cs
index e4e9f735f..4786fbdad 100644
--- a/DisCatSharp.Hosting/IDiscordHostedService.cs
+++ b/DisCatSharp.Hosting/IDiscordHostedService.cs
@@ -1,42 +1,42 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Hosting;
/// <summary>
/// Contract required for <see cref="DiscordClient"/> to work in a web hosting environment
/// </summary>
public interface IDiscordHostedService : Microsoft.Extensions.Hosting.IHostedService
{
/// <summary>
/// Reference to connected client
/// </summary>
DiscordClient Client { get; }
}
/// <summary>
/// Contract required for <see cref="DiscordShardedClient"/> to work in a web hosting environment
/// </summary>
public interface IDiscordHostedShardService : Microsoft.Extensions.Hosting.IHostedService
{
DiscordShardedClient ShardedClient { get; }
}
diff --git a/DisCatSharp.Hosting/Properties/AssemblyProperties.cs b/DisCatSharp.Hosting/Properties/AssemblyProperties.cs
index 020e02337..880e70683 100644
--- a/DisCatSharp.Hosting/Properties/AssemblyProperties.cs
+++ b/DisCatSharp.Hosting/Properties/AssemblyProperties.cs
@@ -1,45 +1,45 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("DisCatSharp.ApplicationCommands")]
[assembly: InternalsVisibleTo("DisCatSharp.CommandsNext")]
[assembly: InternalsVisibleTo("DisCatSharp.Common")]
[assembly: InternalsVisibleTo("DisCatSharp.Configuration")]
[assembly: InternalsVisibleTo("DisCatSharp.Configuration.Tests")]
[assembly: InternalsVisibleTo("DisCatSharp")]
[assembly: InternalsVisibleTo("DisCatSharp.Hosting.DependencyInjection")]
[assembly: InternalsVisibleTo("DisCatSharp.Hosting.Tests")]
[assembly: InternalsVisibleTo("DisCatSharp.Interactivity")]
[assembly: InternalsVisibleTo("DisCatSharp.Lavalink")]
[assembly: InternalsVisibleTo("DisCatSharp.Phabricator")]
[assembly: InternalsVisibleTo("DisCatSharp.Support")]
[assembly: InternalsVisibleTo("DisCatSharp.Test")]
[assembly: InternalsVisibleTo("DisCatSharp.VoiceNext")]
[assembly: InternalsVisibleTo("DisCatSharp.VoiceNext.Natives")]
[assembly: InternalsVisibleTo("Nyaw")]
[assembly: InternalsVisibleTo("DisCatSharp.DevTools")]
[assembly: InternalsVisibleTo("DisCatSharp.DocsGenerator")]
[assembly: InternalsVisibleTo("DisCatSharp.StaffApps")]
[assembly: InternalsVisibleTo("Microsoft.DocAsCode")]
[assembly: InternalsVisibleTo("Microsoft.DocAsCode.Metadata.ManagedReference")]
diff --git a/DisCatSharp.Interactivity/Enums/ButtonPaginationBehavior.cs b/DisCatSharp.Interactivity/Enums/ButtonPaginationBehavior.cs
index ad4e9108d..c302be9ac 100644
--- a/DisCatSharp.Interactivity/Enums/ButtonPaginationBehavior.cs
+++ b/DisCatSharp.Interactivity/Enums/ButtonPaginationBehavior.cs
@@ -1,46 +1,46 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Interactivity.Enums;
/// <summary>
/// Represents options of how to handle pagination timing out.
/// </summary>
public enum ButtonPaginationBehavior
{
/// <summary>
/// The buttons should be disabled when pagination times out.
/// </summary>
Disable,
/// <summary>
/// The buttons should be left as is when pagination times out.
/// </summary>
Ignore,
/// <summary>
/// The entire message should be deleted when pagination times out.
/// </summary>
DeleteMessage,
/// <summary>
/// The buttons should be removed entirely when pagination times out.
/// </summary>
DeleteButtons
}
diff --git a/DisCatSharp.Interactivity/Enums/InteractionResponseBehavior.cs b/DisCatSharp.Interactivity/Enums/InteractionResponseBehavior.cs
index fee53e7ab..57ed1c4de 100644
--- a/DisCatSharp.Interactivity/Enums/InteractionResponseBehavior.cs
+++ b/DisCatSharp.Interactivity/Enums/InteractionResponseBehavior.cs
@@ -1,42 +1,42 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Interactivity.Enums;
/// <summary>
/// The interaction response behavior.
/// </summary>
public enum InteractionResponseBehavior
{
/// <summary>
/// Indicates that invalid input should be ignored when waiting for interactions. This will cause the interaction to fail.
/// </summary>
Ignore,
/// <summary>
/// Indicates that invalid input should be ACK'd. The interaction will succeed, but nothing will happen.
/// </summary>
Ack,
/// <summary>
/// Indicates that invalid input should warrant an ephemeral error message.
/// </summary>
Respond
}
diff --git a/DisCatSharp.Interactivity/Enums/Modal/ModalPage.cs b/DisCatSharp.Interactivity/Enums/Modal/ModalPage.cs
index 78b2ad23c..d1a106064 100644
--- a/DisCatSharp.Interactivity/Enums/Modal/ModalPage.cs
+++ b/DisCatSharp.Interactivity/Enums/Modal/ModalPage.cs
@@ -1,60 +1,60 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.Enums;
namespace DisCatSharp.Interactivity.Enums;
/// <summary>
/// A modal page.
/// </summary>
public class ModalPage
{
/// <summary>
/// Creates a new modal page for the paginated modal builder.
/// </summary>
/// <param name="modal">The modal to display.</param>
/// <param name="openButton">The button to display to open the current page. This is skipped if possible.</param>
/// <param name="openText">The text to display to open the current page. This is skipped if possible.</param>
public ModalPage(DiscordInteractionModalBuilder modal, DiscordButtonComponent openButton = null, string openText = null)
{
this.Modal = modal;
this.OpenButton = openButton ?? new DiscordButtonComponent(ButtonStyle.Primary, null, "Open next page", false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("📄")));
this.OpenMessage = new DiscordInteractionResponseBuilder().WithContent(openText ?? "`Click the button below to continue to the next page.`").AsEphemeral();
}
/// <summary>
/// The modal that will be displayed.
/// </summary>
public DiscordInteractionModalBuilder Modal { get; set; }
/// <summary>
/// The button that will be displayed on the ephemeral message.
/// </summary>
public DiscordButtonComponent OpenButton { get; set; }
/// <summary>
/// The ephemeral message to display for this page.
/// </summary>
public DiscordInteractionResponseBuilder OpenMessage { get; set; }
}
diff --git a/DisCatSharp.Interactivity/Enums/Modal/PaginatedModalResponse.cs b/DisCatSharp.Interactivity/Enums/Modal/PaginatedModalResponse.cs
index 847a72990..97f97e4aa 100644
--- a/DisCatSharp.Interactivity/Enums/Modal/PaginatedModalResponse.cs
+++ b/DisCatSharp.Interactivity/Enums/Modal/PaginatedModalResponse.cs
@@ -1,48 +1,48 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Interactivity.Enums;
/// <summary>
/// A response from the paginated modal response
/// </summary>
public class PaginatedModalResponse
{
/// <summary>
/// The responses. The key is the customid of each component.
/// </summary>
public IReadOnlyDictionary<string, string> Responses { get; internal set; }
/// <summary>
/// The last interaction. This is automatically replied to with a ephemeral "thinking" state. Use EditOriginalResponseAsync to modify this.
/// </summary>
public DiscordInteraction Interaction { get; internal set; }
/// <summary>
/// Whether the interaction timed out.
/// </summary>
public bool TimedOut { get; internal set; }
}
diff --git a/DisCatSharp.Interactivity/Enums/PaginationBehaviour.cs b/DisCatSharp.Interactivity/Enums/PaginationBehaviour.cs
index fffeeebab..31e8a7f10 100644
--- a/DisCatSharp.Interactivity/Enums/PaginationBehaviour.cs
+++ b/DisCatSharp.Interactivity/Enums/PaginationBehaviour.cs
@@ -1,40 +1,40 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Interactivity.Enums;
/// <summary>
/// Specifies how pagination will handle advancing past the first and last pages.
/// </summary>
public enum PaginationBehaviour
{
/// <summary>
/// Going forward beyond the last page will loop back to the first page.
/// Likewise, going back from the first page will loop around to the last page.
/// </summary>
WrapAround = 0,
/// <summary>
/// Attempting to go beyond the first or last page will be ignored.
/// </summary>
Ignore = 1
}
diff --git a/DisCatSharp.Interactivity/Enums/PaginationDeletion.cs b/DisCatSharp.Interactivity/Enums/PaginationDeletion.cs
index 4dbec498e..f35d40471 100644
--- a/DisCatSharp.Interactivity/Enums/PaginationDeletion.cs
+++ b/DisCatSharp.Interactivity/Enums/PaginationDeletion.cs
@@ -1,44 +1,44 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Interactivity.Enums;
/// <summary>
/// Specifies what should be done once pagination times out.
/// </summary>
public enum PaginationDeletion
{
/// <summary>
/// Reaction emojis will be deleted on timeout.
/// </summary>
DeleteEmojis = 0,
/// <summary>
/// Reaction emojis will not be deleted on timeout.
/// </summary>
KeepEmojis = 1,
/// <summary>
/// The message will be completely deleted on timeout.
/// </summary>
DeleteMessage = 2
}
diff --git a/DisCatSharp.Interactivity/Enums/PollBehaviour.cs b/DisCatSharp.Interactivity/Enums/PollBehaviour.cs
index 564223781..1c7ff04ec 100644
--- a/DisCatSharp.Interactivity/Enums/PollBehaviour.cs
+++ b/DisCatSharp.Interactivity/Enums/PollBehaviour.cs
@@ -1,39 +1,39 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Interactivity.Enums;
/// <summary>
/// Specifies what should be done when a poll times out.
/// </summary>
public enum PollBehaviour
{
/// <summary>
/// Reaction emojis will not be deleted.
/// </summary>
KeepEmojis = 0,
/// <summary>
/// Reaction emojis will be deleted.
/// </summary>
DeleteEmojis = 1
}
diff --git a/DisCatSharp.Interactivity/Enums/SplitType.cs b/DisCatSharp.Interactivity/Enums/SplitType.cs
index 586fbf6bc..036589939 100644
--- a/DisCatSharp.Interactivity/Enums/SplitType.cs
+++ b/DisCatSharp.Interactivity/Enums/SplitType.cs
@@ -1,39 +1,39 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Interactivity.Enums;
/// <summary>
/// Specifies how to split a string.
/// </summary>
public enum SplitType
{
/// <summary>
/// Splits string per 500 characters.
/// </summary>
Character,
/// <summary>
/// Splits string per 15 lines.
/// </summary>
Line
}
diff --git a/DisCatSharp.Interactivity/EventHandling/Components/ComponentEventWaiter.cs b/DisCatSharp.Interactivity/EventHandling/Components/ComponentEventWaiter.cs
index cf3c66b56..5f3b4995b 100644
--- a/DisCatSharp.Interactivity/EventHandling/Components/ComponentEventWaiter.cs
+++ b/DisCatSharp.Interactivity/EventHandling/Components/ComponentEventWaiter.cs
@@ -1,152 +1,152 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 ConcurrentCollections;
using DisCatSharp.Entities;
using DisCatSharp.Enums;
using DisCatSharp.EventArgs;
using DisCatSharp.Interactivity.Enums;
using Microsoft.Extensions.Logging;
namespace DisCatSharp.Interactivity.EventHandling;
/// <summary>
/// A component-based version of <see cref="EventWaiter{T}"/>
/// </summary>
internal class ComponentEventWaiter : IDisposable
{
private readonly DiscordClient _client;
private readonly ConcurrentHashSet<ComponentMatchRequest> _matchRequests = new();
private readonly ConcurrentHashSet<ComponentCollectRequest> _collectRequests = new();
private readonly DiscordFollowupMessageBuilder _message;
private readonly InteractivityConfiguration _config;
/// <summary>
/// Initializes a new instance of the <see cref="ComponentEventWaiter"/> class.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="config">The config.</param>
public ComponentEventWaiter(DiscordClient client, InteractivityConfiguration config)
{
this._client = client;
this._client.ComponentInteractionCreated += this.Handle;
this._config = config;
this._message = new DiscordFollowupMessageBuilder { Content = config.ResponseMessage ?? "This message was not meant for you.", IsEphemeral = true };
}
/// <summary>
/// Waits for a specified <see cref="ComponentMatchRequest"/>'s predicate to be fulfilled.
/// </summary>
/// <param name="request">The request to wait for.</param>
/// <returns>The returned args, or null if it timed out.</returns>
public async Task<ComponentInteractionCreateEventArgs> WaitForMatchAsync(ComponentMatchRequest request)
{
this._matchRequests.Add(request);
try
{
return await request.Tcs.Task.ConfigureAwait(false);
}
catch (Exception e)
{
this._client.Logger.LogError(InteractivityEvents.InteractivityWaitError, e, "An exception was thrown while waiting for components.");
return null;
}
finally
{
this._matchRequests.TryRemove(request);
}
}
/// <summary>
/// Collects reactions and returns the result when the <see cref="ComponentMatchRequest"/>'s cancellation token is canceled.
/// </summary>
/// <param name="request">The request to wait on.</param>
/// <returns>The result from request's predicate over the period of time leading up to the token's cancellation.</returns>
public async Task<IReadOnlyList<ComponentInteractionCreateEventArgs>> CollectMatchesAsync(ComponentCollectRequest request)
{
this._collectRequests.Add(request);
try
{
await request.Tcs.Task.ConfigureAwait(false);
}
catch (Exception e)
{
this._client.Logger.LogError(InteractivityEvents.InteractivityCollectorError, e, "There was an error while collecting component event args.");
}
finally
{
this._collectRequests.TryRemove(request);
}
return request.Collected.ToArray();
}
/// <summary>
/// Handles the waiter.
/// </summary>
/// <param name="_">The client.</param>
/// <param name="args">The args.</param>
private async Task Handle(DiscordClient _, ComponentInteractionCreateEventArgs args)
{
foreach (var mreq in this._matchRequests)
{
if (mreq.Message == args.Message && mreq.IsMatch(args))
mreq.Tcs.TrySetResult(args);
else if (this._config.ResponseBehavior is InteractionResponseBehavior.Respond)
await args.Interaction.CreateFollowupMessageAsync(this._message).ConfigureAwait(false);
}
foreach (var creq in this._collectRequests)
{
if (creq.Message == args.Message && creq.IsMatch(args))
{
await args.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate).ConfigureAwait(false);
if (creq.IsMatch(args))
creq.Collected.Add(args);
else if (this._config.ResponseBehavior is InteractionResponseBehavior.Respond)
await args.Interaction.CreateFollowupMessageAsync(this._message).ConfigureAwait(false);
}
}
}
/// <summary>
/// Disposes the waiter.
/// </summary>
public void Dispose()
{
this._matchRequests.Clear();
this._collectRequests.Clear();
this._client.ComponentInteractionCreated -= this.Handle;
}
}
diff --git a/DisCatSharp.Interactivity/EventHandling/Components/ComponentPaginator.cs b/DisCatSharp.Interactivity/EventHandling/Components/ComponentPaginator.cs
index cb53a053b..fb83cef1c 100644
--- a/DisCatSharp.Interactivity/EventHandling/Components/ComponentPaginator.cs
+++ b/DisCatSharp.Interactivity/EventHandling/Components/ComponentPaginator.cs
@@ -1,183 +1,183 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using DisCatSharp.EventArgs;
using DisCatSharp.Interactivity.Enums;
using Microsoft.Extensions.Logging;
namespace DisCatSharp.Interactivity.EventHandling;
/// <summary>
/// The component paginator.
/// </summary>
internal class ComponentPaginator : IPaginator
{
private readonly DiscordClient _client;
private readonly InteractivityConfiguration _config;
private readonly DiscordMessageBuilder _builder = new();
private readonly Dictionary<ulong, IPaginationRequest> _requests = new();
/// <summary>
/// Initializes a new instance of the <see cref="ComponentPaginator"/> class.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="config">The config.</param>
public ComponentPaginator(DiscordClient client, InteractivityConfiguration config)
{
this._client = client;
this._client.ComponentInteractionCreated += this.Handle;
this._config = config;
}
/// <summary>
/// Does the pagination async.
/// </summary>
/// <param name="request">The request.</param>
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.");
}
}
}
/// <summary>
/// Disposes the paginator.
/// </summary>
public void Dispose() => this._client.ComponentInteractionCreated -= this.Handle;
/// <summary>
/// Handles the pagination event.
/// </summary>
/// <param name="_">The client.</param>
/// <param name="e">The event arguments.</param>
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 DiscordFollowupMessageBuilder { 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 //
await this.HandlePaginationAsync(req, e).ConfigureAwait(false);
}
/// <summary>
/// Handles the pagination async.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="args">The arguments.</param>
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/ModalEventWaiter.cs b/DisCatSharp.Interactivity/EventHandling/Components/ModalEventWaiter.cs
index 3696d8260..d37c9c94c 100644
--- a/DisCatSharp.Interactivity/EventHandling/Components/ModalEventWaiter.cs
+++ b/DisCatSharp.Interactivity/EventHandling/Components/ModalEventWaiter.cs
@@ -1,110 +1,110 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 ConcurrentCollections;
using DisCatSharp.Entities;
using DisCatSharp.EventArgs;
using DisCatSharp.Interactivity.Enums;
using Microsoft.Extensions.Logging;
namespace DisCatSharp.Interactivity.EventHandling;
/// <summary>
/// A modal-based version of <see cref="EventWaiter{T}"/>
/// </summary>
internal class ModalEventWaiter : IDisposable
{
private readonly DiscordClient _client;
private readonly ConcurrentHashSet<ModalMatchRequest> _modalMatchRequests = new();
private readonly DiscordFollowupMessageBuilder _message;
private readonly InteractivityConfiguration _config;
/// <summary>
/// Initializes a new instance of the <see cref="ComponentEventWaiter"/> class.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="config">The config.</param>
public ModalEventWaiter(DiscordClient client, InteractivityConfiguration config)
{
this._client = client;
this._client.ComponentInteractionCreated += this.Handle;
this._config = config;
this._message = new DiscordFollowupMessageBuilder { Content = config.ResponseMessage ?? "This modal was not meant for you.", IsEphemeral = true };
}
/// <summary>
/// Waits for a specified <see cref="ModalMatchRequest"/>'s predicate to be fulfilled.
/// </summary>
/// <param name="request">The request to wait for.</param>
/// <returns>The returned args, or null if it timed out.</returns>
public async Task<ComponentInteractionCreateEventArgs> WaitForModalMatchAsync(ModalMatchRequest request)
{
this._modalMatchRequests.Add(request);
try
{
return await request.Tcs.Task.ConfigureAwait(false);
}
catch (Exception e)
{
this._client.Logger.LogError(InteractivityEvents.InteractivityWaitError, e, "An exception was thrown while waiting for modals.");
return null;
}
finally
{
this._modalMatchRequests.TryRemove(request);
}
}
/// <summary>
/// Handles the waiter.
/// </summary>
/// <param name="_">The client.</param>
/// <param name="args">The args.</param>
private async Task Handle(DiscordClient _, ComponentInteractionCreateEventArgs args)
{
foreach (var mreq in this._modalMatchRequests)
{
if (mreq.CustomId == args.Interaction.Data.CustomId && mreq.IsMatch(args))
mreq.Tcs.TrySetResult(args);
else if (this._config.ResponseBehavior is InteractionResponseBehavior.Respond)
await args.Interaction.CreateFollowupMessageAsync(this._message).ConfigureAwait(false);
}
}
/// <summary>
/// Disposes the waiter.
/// </summary>
public void Dispose()
{
this._modalMatchRequests.Clear();
this._client.ComponentInteractionCreated -= this.Handle;
}
}
diff --git a/DisCatSharp.Interactivity/EventHandling/Components/PaginationButtons.cs b/DisCatSharp.Interactivity/EventHandling/Components/PaginationButtons.cs
index f1e113974..a26a93931 100644
--- a/DisCatSharp.Interactivity/EventHandling/Components/PaginationButtons.cs
+++ b/DisCatSharp.Interactivity/EventHandling/Components/PaginationButtons.cs
@@ -1,94 +1,94 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.Enums;
namespace DisCatSharp.Interactivity.EventHandling;
/// <summary>
/// The pagination buttons.
/// </summary>
public class PaginationButtons
{
/// <summary>
/// Gets or sets the skip left button.
/// </summary>
public DiscordButtonComponent SkipLeft { internal get; set; }
/// <summary>
/// Gets or sets the left button.
/// </summary>
public DiscordButtonComponent Left { internal get; set; }
/// <summary>
/// Gets or sets the stop button.
/// </summary>
public DiscordButtonComponent Stop { internal get; set; }
/// <summary>
/// Gets or sets the right button.
/// </summary>
public DiscordButtonComponent Right { internal get; set; }
/// <summary>
/// Gets or sets the skip right button.
/// </summary>
public DiscordButtonComponent SkipRight { internal get; set; }
/// <summary>
/// Gets the button array.
/// </summary>
internal DiscordButtonComponent[] ButtonArray => new[]
{
this.SkipLeft,
this.Left,
this.Stop,
this.Right,
this.SkipRight
};
/// <summary>
/// Initializes a new instance of the <see cref="PaginationButtons"/> class.
/// </summary>
public PaginationButtons()
{
this.SkipLeft = new DiscordButtonComponent(ButtonStyle.Secondary, "leftskip", null, false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("⏮")));
this.Left = new DiscordButtonComponent(ButtonStyle.Secondary, "left", null, false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("◀")));
this.Stop = new DiscordButtonComponent(ButtonStyle.Secondary, "stop", null, false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("⏹")));
this.Right = new DiscordButtonComponent(ButtonStyle.Secondary, "right", null, false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("▶")));
this.SkipRight = new DiscordButtonComponent(ButtonStyle.Secondary, "rightskip", null, false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("⏭")));
}
/// <summary>
/// Initializes a new instance of the <see cref="PaginationButtons"/> class.
/// </summary>
/// <param name="other">The other <see cref="PaginationButtons"/>.</param>
public PaginationButtons(PaginationButtons other)
{
this.Stop = new DiscordButtonComponent(other.Stop);
this.Left = new DiscordButtonComponent(other.Left);
this.Right = new DiscordButtonComponent(other.Right);
this.SkipLeft = new DiscordButtonComponent(other.SkipLeft);
this.SkipRight = new DiscordButtonComponent(other.SkipRight);
}
}
diff --git a/DisCatSharp.Interactivity/EventHandling/Components/Requests/ButtonPaginationRequest.cs b/DisCatSharp.Interactivity/EventHandling/Components/Requests/ButtonPaginationRequest.cs
index e2d4e5ef4..ae2c03534 100644
--- a/DisCatSharp.Interactivity/EventHandling/Components/Requests/ButtonPaginationRequest.cs
+++ b/DisCatSharp.Interactivity/EventHandling/Components/Requests/ButtonPaginationRequest.cs
@@ -1,244 +1,244 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// The button pagination request.
/// </summary>
internal class ButtonPaginationRequest : IPaginationRequest
{
private int _index;
private readonly List<Page> _pages = new();
private readonly TaskCompletionSource<bool> _tcs = new();
private readonly CancellationToken _token;
private readonly DiscordUser _user;
private readonly DiscordMessage _message;
private readonly PaginationButtons _buttons;
private readonly PaginationBehaviour _wrapBehavior;
private readonly ButtonPaginationBehavior _behaviorBehavior;
/// <summary>
/// Initializes a new instance of the <see cref="ButtonPaginationRequest"/> class.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="user">The user.</param>
/// <param name="behavior">The behavior.</param>
/// <param name="buttonBehavior">The button behavior.</param>
/// <param name="buttons">The buttons.</param>
/// <param name="pages">The pages.</param>
/// <param name="token">The token.</param>
public ButtonPaginationRequest(DiscordMessage message, DiscordUser user,
PaginationBehaviour behavior, ButtonPaginationBehavior buttonBehavior,
PaginationButtons buttons, IEnumerable<Page> pages, CancellationToken token)
{
this._user = user;
this._token = token;
this._buttons = new PaginationButtons(buttons);
this._message = message;
this._wrapBehavior = behavior;
this._behaviorBehavior = buttonBehavior;
this._pages.AddRange(pages);
this._token.Register(() => this._tcs.TrySetResult(false));
}
/// <summary>
/// Gets the page count.
/// </summary>
public int PageCount => this._pages.Count;
/// <summary>
/// Gets the page.
/// </summary>
public Task<Page> GetPageAsync()
{
var page = Task.FromResult(this._pages[this._index]);
if (this.PageCount is 1)
{
this._buttons.SkipLeft.Disable();
this._buttons.Left.Disable();
this._buttons.Right.Disable();
this._buttons.SkipRight.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;
}
/// <summary>
/// Skips the left.
/// </summary>
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;
}
/// <summary>
/// Skips the right.
/// </summary>
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;
}
/// <summary>
/// Gets the next page.
/// </summary>
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;
}
/// <summary>
/// Gets the previous page.
/// </summary>
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;
}
/// <summary>
/// Gets the emojis.
/// </summary>
public Task<PaginationEmojis> GetEmojisAsync() => Task.FromException<PaginationEmojis>(new NotSupportedException("Emojis aren't supported for this request."));
/// <summary>
/// Gets the buttons.
/// </summary>
public Task<IEnumerable<DiscordButtonComponent>> GetButtonsAsync()
=> Task.FromResult((IEnumerable<DiscordButtonComponent>)this._buttons.ButtonArray);
/// <summary>
/// Gets the message.
/// </summary>
public Task<DiscordMessage> GetMessageAsync() => Task.FromResult(this._message);
/// <summary>
/// Gets the user.
/// </summary>
public Task<DiscordUser> GetUserAsync() => Task.FromResult(this._user);
/// <summary>
/// Gets the task completion source.
/// </summary>
public Task<TaskCompletionSource<bool>> GetTaskCompletionSourceAsync() => Task.FromResult(this._tcs);
/// <summary>
/// Does the cleanup.
/// </summary>
public async Task DoCleanupAsync()
{
switch (this._behaviorBehavior)
{
case ButtonPaginationBehavior.Disable:
var buttons = this._buttons.ButtonArray.Select(b => b.Disable());
var builder = new DiscordMessageBuilder()
.WithContent(this._pages[this._index].Content)
.AddEmbed(this._pages[this._index].Embed)
.AddComponents(buttons);
await builder.ModifyAsync(this._message).ConfigureAwait(false);
break;
case ButtonPaginationBehavior.DeleteButtons:
builder = new DiscordMessageBuilder()
.WithContent(this._pages[this._index].Content)
.AddEmbed(this._pages[this._index].Embed);
await builder.ModifyAsync(this._message).ConfigureAwait(false);
break;
case ButtonPaginationBehavior.DeleteMessage:
await this._message.DeleteAsync().ConfigureAwait(false);
break;
case ButtonPaginationBehavior.Ignore:
break;
}
}
}
diff --git a/DisCatSharp.Interactivity/EventHandling/Components/Requests/ComponentCollectRequest.cs b/DisCatSharp.Interactivity/EventHandling/Components/Requests/ComponentCollectRequest.cs
index 6ee2d5b59..6c5b65be5 100644
--- a/DisCatSharp.Interactivity/EventHandling/Components/Requests/ComponentCollectRequest.cs
+++ b/DisCatSharp.Interactivity/EventHandling/Components/Requests/ComponentCollectRequest.cs
@@ -1,49 +1,49 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.Entities;
using DisCatSharp.EventArgs;
namespace DisCatSharp.Interactivity.EventHandling;
/// <summary>
/// Represents a component event that is being waited for.
/// </summary>
internal sealed class ComponentCollectRequest : ComponentMatchRequest
{
/// <summary>
/// Gets the collected.
/// </summary>
public ConcurrentBag<ComponentInteractionCreateEventArgs> Collected { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="ComponentCollectRequest"/> class.
/// </summary>
/// <param name="message"></param>
/// <param name="predicate">The predicate.</param>
/// <param name="cancellation">The cancellation token.</param>
public ComponentCollectRequest(DiscordMessage message, Func<ComponentInteractionCreateEventArgs, bool> predicate, CancellationToken cancellation) : base(message, predicate, cancellation) { }
}
diff --git a/DisCatSharp.Interactivity/EventHandling/Components/Requests/ComponentMatchRequest.cs b/DisCatSharp.Interactivity/EventHandling/Components/Requests/ComponentMatchRequest.cs
index 11a9cfd5e..39e1c9d81 100644
--- a/DisCatSharp.Interactivity/EventHandling/Components/Requests/ComponentMatchRequest.cs
+++ b/DisCatSharp.Interactivity/EventHandling/Components/Requests/ComponentMatchRequest.cs
@@ -1,69 +1,69 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Entities;
using DisCatSharp.EventArgs;
namespace DisCatSharp.Interactivity.EventHandling;
/// <summary>
/// Represents a match that is being waited for.
/// </summary>
internal class ComponentMatchRequest
{
/// <summary>
/// The id to wait on. This should be uniquely formatted to avoid collisions.
/// </summary>
public DiscordMessage Message { get; private set; }
/// <summary>
/// The completion source that represents the result of the match.
/// </summary>
public TaskCompletionSource<ComponentInteractionCreateEventArgs> Tcs { get; private set; } = new();
protected readonly CancellationToken Cancellation;
protected readonly Func<ComponentInteractionCreateEventArgs, bool> Predicate;
/// <summary>
/// Initializes a new instance of the <see cref="ComponentMatchRequest"/> class.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="predicate">The predicate.</param>
/// <param name="cancellation">The cancellation token.</param>
public ComponentMatchRequest(DiscordMessage message, Func<ComponentInteractionCreateEventArgs, bool> predicate, CancellationToken cancellation)
{
this.Message = message;
this.Predicate = predicate;
this.Cancellation = cancellation;
this.Cancellation.Register(() => this.Tcs.TrySetResult(null)); // TrySetCancelled would probably be better but I digress ~Velvet //
}
/// <summary>
/// Whether it is a match.
/// </summary>
/// <param name="args">The arguments.</param>
public bool IsMatch(ComponentInteractionCreateEventArgs args) => this.Predicate(args);
}
diff --git a/DisCatSharp.Interactivity/EventHandling/Components/Requests/InteractionPaginationRequest.cs b/DisCatSharp.Interactivity/EventHandling/Components/Requests/InteractionPaginationRequest.cs
index eb14ff198..d23949dc3 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-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// The interaction pagination request.
/// </summary>
internal class InteractionPaginationRequest : IPaginationRequest
{
private int _index;
private readonly List<Page> _pages = new();
private readonly TaskCompletionSource<bool> _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;
/// <summary>
/// Initializes a new instance of the <see cref="InteractionPaginationRequest"/> class.
/// </summary>
/// <param name="interaction">The interaction.</param>
/// <param name="message">The message.</param>
/// <param name="user">The user.</param>
/// <param name="behavior">The behavior.</param>
/// <param name="behaviorBehavior">The behavior behavior.</param>
/// <param name="buttons">The buttons.</param>
/// <param name="pages">The pages.</param>
/// <param name="token">The token.</param>
public InteractionPaginationRequest(DiscordInteraction interaction, DiscordMessage message, DiscordUser user,
PaginationBehaviour behavior, ButtonPaginationBehavior behaviorBehavior,
PaginationButtons buttons, IEnumerable<Page> pages, CancellationToken token)
{
this._user = user;
this._token = token;
this._buttons = new PaginationButtons(buttons);
this._message = message;
this._wrapBehavior = behavior;
this._behaviorBehavior = behaviorBehavior;
this._pages.AddRange(pages);
this.RegenerateCts(interaction);
this._token.Register(() => this._tcs.TrySetResult(false));
}
/// <summary>
/// Gets the page count.
/// </summary>
public int PageCount => this._pages.Count;
/// <summary>
/// Regenerates the cts.
/// </summary>
/// <param name="interaction">The interaction.</param>
internal void RegenerateCts(DiscordInteraction interaction)
{
this._interactionCts?.Dispose();
this._lastInteraction = interaction;
this._interactionCts = new CancellationTokenSource(TimeSpan.FromSeconds((60 * 15) - 5));
this._interactionCts.Token.Register(() => this._tcs.TrySetResult(false));
}
/// <summary>
/// Gets the page.
/// </summary>
public Task<Page> 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;
}
/// <summary>
/// Skips the left page.
/// </summary>
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;
}
/// <summary>
/// Skips the right page.
/// </summary>
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;
}
/// <summary>
/// Gets the next page.
/// </summary>
/// <returns>A Task.</returns>
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;
}
/// <summary>
/// Gets the previous page.
/// </summary>
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;
}
/// <summary>
/// Gets the emojis.
/// </summary>
public Task<PaginationEmojis> GetEmojisAsync()
=> Task.FromException<PaginationEmojis>(new NotSupportedException("Emojis aren't supported for this request."));
/// <summary>
/// Gets the buttons.
/// </summary>
public Task<IEnumerable<DiscordButtonComponent>> GetButtonsAsync()
=> Task.FromResult((IEnumerable<DiscordButtonComponent>)this._buttons.ButtonArray);
/// <summary>
/// Gets the message.
/// </summary>
public Task<DiscordMessage> GetMessageAsync() => Task.FromResult(this._message);
/// <summary>
/// Gets the user.
/// </summary>
public Task<DiscordUser> GetUserAsync() => Task.FromResult(this._user);
/// <summary>
/// Gets the task completion source.
/// </summary>
public Task<TaskCompletionSource<bool>> GetTaskCompletionSourceAsync() => Task.FromResult(this._tcs);
/// <summary>
/// Cleanup.
/// </summary>
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 e447f48a8..cc0e5c678 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-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a match that is being waited for.
/// </summary>
internal class ModalMatchRequest
{
/// <summary>
/// The id to wait on. This should be uniquely formatted to avoid collisions.
/// </summary>
public string CustomId { get; private set; }
/// <summary>
/// The completion source that represents the result of the match.
/// </summary>
public TaskCompletionSource<ComponentInteractionCreateEventArgs> Tcs { get; private set; } = new();
protected readonly CancellationToken Cancellation;
protected readonly Func<ComponentInteractionCreateEventArgs, bool> Predicate;
/// <summary>
/// Initializes a new instance of the <see cref="ModalMatchRequest"/> class.
/// </summary>
/// <param name="customId">The custom id.</param>
/// <param name="predicate">The predicate.</param>
/// <param name="cancellation">The cancellation token.</param>
public ModalMatchRequest(string customId, Func<ComponentInteractionCreateEventArgs, bool> predicate, CancellationToken cancellation)
{
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 //
}
/// <summary>
/// Whether it is a match.
/// </summary>
/// <param name="args">The arguments.</param>
public bool IsMatch(ComponentInteractionCreateEventArgs args) => this.Predicate(args);
}
diff --git a/DisCatSharp.Interactivity/EventHandling/EventWaiter.cs b/DisCatSharp.Interactivity/EventHandling/EventWaiter.cs
index 4c2dfca1f..c0a792124 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-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T"></typeparam>
internal class EventWaiter<T> : IDisposable where T : AsyncEventArgs
{
DiscordClient _client;
AsyncEvent<DiscordClient, T> _event;
AsyncEventHandler<DiscordClient, T> _handler;
ConcurrentHashSet<MatchRequest<T>> _matchRequests;
ConcurrentHashSet<CollectRequest<T>> _collectRequests;
bool _disposed;
/// <summary>
/// Creates a new EventWaiter object.
/// </summary>
/// <param name="client">Your DiscordClient</param>
public EventWaiter(DiscordClient client)
{
this._client = client;
var tinfo = this._client.GetType().GetTypeInfo();
var handler = tinfo.DeclaredFields.First(x => x.FieldType == typeof(AsyncEvent<DiscordClient, T>));
this._matchRequests = new ConcurrentHashSet<MatchRequest<T>>();
this._collectRequests = new ConcurrentHashSet<CollectRequest<T>>();
this._event = (AsyncEvent<DiscordClient, T>)handler.GetValue(this._client);
this._handler = new AsyncEventHandler<DiscordClient, T>(this.HandleEvent);
this._event.Register(this._handler);
}
/// <summary>
/// Waits for a match to a specific request, else returns null.
/// </summary>
/// <param name="request">Request to match</param>
/// <returns></returns>
public async Task<T> WaitForMatchAsync(MatchRequest<T> request)
{
T result = null;
this._matchRequests.Add(request);
try
{
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;
}
/// <summary>
/// Collects the matches async.
/// </summary>
/// <param name="request">The request.</param>
public async Task<ReadOnlyCollection<T>> CollectMatchesAsync(CollectRequest<T> request)
{
ReadOnlyCollection<T> result = null;
this._collectRequests.Add(request);
try
{
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<T>(new HashSet<T>(request.Collected).ToList());
request.Dispose();
this._collectRequests.TryRemove(request);
}
return result;
}
/// <summary>
/// Handles the event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="eventArgs">The event's arguments.</param>
private Task HandleEvent(DiscordClient client, T eventArgs)
{
if (!this._disposed)
{
foreach (var req in this._matchRequests)
{
if (req.Predicate(eventArgs))
{
req.Tcs.TrySetResult(eventArgs);
}
}
foreach (var req in this._collectRequests)
{
if (req.Predicate(eventArgs))
{
req.Collected.Add(eventArgs);
}
}
}
return Task.CompletedTask;
}
~EventWaiter()
{
this.Dispose();
}
/// <summary>
/// Disposes this EventWaiter
/// </summary>
public void Dispose()
{
this._disposed = true;
this._event?.Unregister(this._handler);
this._event = null;
this._handler = null;
this._client = null;
this._matchRequests?.Clear();
this._collectRequests?.Clear();
this._matchRequests = null;
this._collectRequests = null;
GC.SuppressFinalize(this);
}
}
diff --git a/DisCatSharp.Interactivity/EventHandling/IPaginator.cs b/DisCatSharp.Interactivity/EventHandling/IPaginator.cs
index f9f0902b1..adbcac975 100644
--- a/DisCatSharp.Interactivity/EventHandling/IPaginator.cs
+++ b/DisCatSharp.Interactivity/EventHandling/IPaginator.cs
@@ -1,43 +1,43 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Threading.Tasks;
namespace DisCatSharp.Interactivity.EventHandling;
/// <summary>
/// The paginator.
/// </summary>
internal interface IPaginator
{
/// <summary>
/// Paginates.
/// </summary>
/// <param name="request">The request to paginate.</param>
/// <returns>A task that completes when the pagination finishes or times out.</returns>
Task DoPaginationAsync(IPaginationRequest request);
/// <summary>
/// Disposes this EventWaiter
/// </summary>
void Dispose();
}
diff --git a/DisCatSharp.Interactivity/EventHandling/Paginator.cs b/DisCatSharp.Interactivity/EventHandling/Paginator.cs
index 41386de81..3d414d895 100644
--- a/DisCatSharp.Interactivity/EventHandling/Paginator.cs
+++ b/DisCatSharp.Interactivity/EventHandling/Paginator.cs
@@ -1,305 +1,305 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 ConcurrentCollections;
using DisCatSharp.Entities;
using DisCatSharp.Enums;
using DisCatSharp.EventArgs;
using DisCatSharp.Interactivity.Enums;
using Microsoft.Extensions.Logging;
namespace DisCatSharp.Interactivity.EventHandling;
/// <summary>
/// The paginator.
/// </summary>
internal class Paginator : IPaginator
{
DiscordClient _client;
ConcurrentHashSet<IPaginationRequest> _requests;
/// <summary>
/// Creates a new EventWaiter object.
/// </summary>
/// <param name="client">Discord client</param>
public Paginator(DiscordClient client)
{
this._client = client;
this._requests = new ConcurrentHashSet<IPaginationRequest>();
this._client.MessageReactionAdded += this.HandleReactionAdd;
this._client.MessageReactionRemoved += this.HandleReactionRemove;
this._client.MessageReactionsCleared += this.HandleReactionClear;
}
/// <summary>
/// Dos the pagination async.
/// </summary>
/// <param name="request">The request.</param>
public async Task DoPaginationAsync(IPaginationRequest request)
{
await this.ResetReactionsAsync(request).ConfigureAwait(false);
this._requests.Add(request);
try
{
var tcs = await request.GetTaskCompletionSourceAsync().ConfigureAwait(false);
await tcs.Task.ConfigureAwait(false);
}
catch (Exception ex)
{
this._client.Logger.LogError(InteractivityEvents.InteractivityPaginationError, ex, "Exception occurred while paginating");
}
finally
{
this._requests.TryRemove(request);
try
{
await request.DoCleanupAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
this._client.Logger.LogError(InteractivityEvents.InteractivityPaginationError, ex, "Exception occurred while paginating");
}
}
}
/// <summary>
/// Handles the reaction add.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="eventArgs">The event's arguments.</param>
private Task HandleReactionAdd(DiscordClient client, MessageReactionAddEventArgs eventArgs)
{
if (this._requests.Count == 0)
return Task.CompletedTask;
_ = Task.Run(async () =>
{
foreach (var req in this._requests)
{
var emojis = await req.GetEmojisAsync().ConfigureAwait(false);
var msg = await req.GetMessageAsync().ConfigureAwait(false);
var usr = await req.GetUserAsync().ConfigureAwait(false);
if (msg.Id == eventArgs.Message.Id)
{
if (eventArgs.User.Id == usr.Id)
{
if (req.PageCount > 1 &&
(eventArgs.Emoji == emojis.Left ||
eventArgs.Emoji == emojis.SkipLeft ||
eventArgs.Emoji == emojis.Right ||
eventArgs.Emoji == emojis.SkipRight ||
eventArgs.Emoji == emojis.Stop))
{
await this.PaginateAsync(req, eventArgs.Emoji).ConfigureAwait(false);
}
else if (eventArgs.Emoji == emojis.Stop &&
req is PaginationRequest paginationRequest &&
paginationRequest.PaginationDeletion == PaginationDeletion.DeleteMessage)
{
await this.PaginateAsync(req, eventArgs.Emoji).ConfigureAwait(false);
}
else
{
await msg.DeleteReactionAsync(eventArgs.Emoji, eventArgs.User).ConfigureAwait(false);
}
}
else if (eventArgs.User.Id != this._client.CurrentUser.Id)
{
if (eventArgs.Emoji != emojis.Left &&
eventArgs.Emoji != emojis.SkipLeft &&
eventArgs.Emoji != emojis.Right &&
eventArgs.Emoji != emojis.SkipRight &&
eventArgs.Emoji != emojis.Stop)
{
await msg.DeleteReactionAsync(eventArgs.Emoji, eventArgs.User).ConfigureAwait(false);
}
}
}
}
});
return Task.CompletedTask;
}
/// <summary>
/// Handles the reaction remove.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="eventArgs">The event's arguments.</param>
private Task HandleReactionRemove(DiscordClient client, MessageReactionRemoveEventArgs eventArgs)
{
if (this._requests.Count == 0)
return Task.CompletedTask;
_ = Task.Run(async () =>
{
foreach (var req in this._requests)
{
var emojis = await req.GetEmojisAsync().ConfigureAwait(false);
var msg = await req.GetMessageAsync().ConfigureAwait(false);
var usr = await req.GetUserAsync().ConfigureAwait(false);
if (msg.Id == eventArgs.Message.Id)
{
if (eventArgs.User.Id == usr.Id)
{
if (req.PageCount > 1 &&
(eventArgs.Emoji == emojis.Left ||
eventArgs.Emoji == emojis.SkipLeft ||
eventArgs.Emoji == emojis.Right ||
eventArgs.Emoji == emojis.SkipRight ||
eventArgs.Emoji == emojis.Stop))
{
await this.PaginateAsync(req, eventArgs.Emoji).ConfigureAwait(false);
}
else if (eventArgs.Emoji == emojis.Stop &&
req is PaginationRequest paginationRequest &&
paginationRequest.PaginationDeletion == PaginationDeletion.DeleteMessage)
{
await this.PaginateAsync(req, eventArgs.Emoji).ConfigureAwait(false);
}
}
}
}
});
return Task.CompletedTask;
}
/// <summary>
/// Handles the reaction clear.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="eventArgs">The eventArgs.</param>
private Task HandleReactionClear(DiscordClient client, MessageReactionsClearEventArgs eventArgs)
{
if (this._requests.Count == 0)
return Task.CompletedTask;
_ = Task.Run(async () =>
{
foreach (var req in this._requests)
{
var msg = await req.GetMessageAsync().ConfigureAwait(false);
if (msg.Id == eventArgs.Message.Id)
{
await this.ResetReactionsAsync(req).ConfigureAwait(false);
}
}
});
return Task.CompletedTask;
}
/// <summary>
/// Resets the reactions async.
/// </summary>
/// <param name="p">The p.</param>
private async Task ResetReactionsAsync(IPaginationRequest p)
{
var msg = await p.GetMessageAsync().ConfigureAwait(false);
var emojis = await p.GetEmojisAsync().ConfigureAwait(false);
var chn = msg.Channel;
var gld = chn?.Guild;
var mbr = gld?.CurrentMember;
if (mbr != null && (chn.PermissionsFor(mbr) & Permissions.ManageMessages) != 0)
await msg.DeleteAllReactionsAsync("Pagination").ConfigureAwait(false);
if (p.PageCount > 1)
{
if (emojis.SkipLeft != null)
await msg.CreateReactionAsync(emojis.SkipLeft).ConfigureAwait(false);
if (emojis.Left != null)
await msg.CreateReactionAsync(emojis.Left).ConfigureAwait(false);
if (emojis.Right != null)
await msg.CreateReactionAsync(emojis.Right).ConfigureAwait(false);
if (emojis.SkipRight != null)
await msg.CreateReactionAsync(emojis.SkipRight).ConfigureAwait(false);
if (emojis.Stop != null)
await msg.CreateReactionAsync(emojis.Stop).ConfigureAwait(false);
}
else if (emojis.Stop != null && p is PaginationRequest paginationRequest && paginationRequest.PaginationDeletion == PaginationDeletion.DeleteMessage)
{
await msg.CreateReactionAsync(emojis.Stop).ConfigureAwait(false);
}
}
/// <summary>
/// Paginates the async.
/// </summary>
/// <param name="p">The p.</param>
/// <param name="emoji">The emoji.</param>
private async Task PaginateAsync(IPaginationRequest p, DiscordEmoji emoji)
{
var emojis = await p.GetEmojisAsync().ConfigureAwait(false);
var msg = await p.GetMessageAsync().ConfigureAwait(false);
if (emoji == emojis.SkipLeft)
await p.SkipLeftAsync().ConfigureAwait(false);
else if (emoji == emojis.Left)
await p.PreviousPageAsync().ConfigureAwait(false);
else if (emoji == emojis.Right)
await p.NextPageAsync().ConfigureAwait(false);
else if (emoji == emojis.SkipRight)
await p.SkipRightAsync().ConfigureAwait(false);
else if (emoji == emojis.Stop)
{
var tcs = await p.GetTaskCompletionSourceAsync().ConfigureAwait(false);
tcs.TrySetResult(true);
return;
}
var page = await p.GetPageAsync().ConfigureAwait(false);
var builder = new DiscordMessageBuilder()
.WithContent(page.Content)
.WithEmbed(page.Embed);
await builder.ModifyAsync(msg).ConfigureAwait(false);
}
~Paginator()
{
this.Dispose();
}
/// <summary>
/// Disposes this EventWaiter
/// </summary>
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/Poller.cs b/DisCatSharp.Interactivity/EventHandling/Poller.cs
index 5c3fc35de..89e91519b 100644
--- a/DisCatSharp.Interactivity/EventHandling/Poller.cs
+++ b/DisCatSharp.Interactivity/EventHandling/Poller.cs
@@ -1,177 +1,177 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using DisCatSharp.EventArgs;
using Microsoft.Extensions.Logging;
namespace DisCatSharp.Interactivity.EventHandling;
/// <summary>
/// The poller.
/// </summary>
internal class Poller
{
DiscordClient _client;
ConcurrentHashSet<PollRequest> _requests;
/// <summary>
/// Creates a new EventWaiter object.
/// </summary>
/// <param name="client">Your DiscordClient</param>
public Poller(DiscordClient client)
{
this._client = client;
this._requests = new ConcurrentHashSet<PollRequest>();
this._client.MessageReactionAdded += this.HandleReactionAdd;
this._client.MessageReactionRemoved += this.HandleReactionRemove;
this._client.MessageReactionsCleared += this.HandleReactionClear;
}
/// <summary>
/// Dos the poll async.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>A Task.</returns>
public async Task<ReadOnlyCollection<PollEmoji>> DoPollAsync(PollRequest request)
{
ReadOnlyCollection<PollEmoji> result = null;
this._requests.Add(request);
try
{
await request.Tcs.Task.ConfigureAwait(false);
}
catch (Exception ex)
{
this._client.Logger.LogError(InteractivityEvents.InteractivityPollError, ex, "Exception occurred while polling");
}
finally
{
result = new ReadOnlyCollection<PollEmoji>(new HashSet<PollEmoji>(request.Collected).ToList());
request.Dispose();
this._requests.TryRemove(request);
}
return result;
}
/// <summary>
/// Handles the reaction add.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="eventArgs">The event's arguments.</param>
/// <returns>A Task.</returns>
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.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;
}
/// <summary>
/// Handles the reaction remove.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="eventArgs">The event's arguments.</param>
/// <returns>A Task.</returns>
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 (eventArgs.User.Id != this._client.CurrentUser.Id)
req.RemoveReaction(eventArgs.Emoji, eventArgs.User);
}
}
return Task.CompletedTask;
}
/// <summary>
/// Handles the reaction clear.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="eventArgs">The event's arguments.</param>
/// <returns>A Task.</returns>
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)
{
req.ClearCollected();
}
}
return Task.CompletedTask;
}
~Poller()
{
this.Dispose();
}
/// <summary>
/// Disposes this EventWaiter
/// </summary>
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 629144a1d..d300fe9b0 100644
--- a/DisCatSharp.Interactivity/EventHandling/ReactionCollector.cs
+++ b/DisCatSharp.Interactivity/EventHandling/ReactionCollector.cs
@@ -1,287 +1,287 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// 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.
/// </summary>
internal class ReactionCollector : IDisposable
{
DiscordClient _client;
AsyncEvent<DiscordClient, MessageReactionAddEventArgs> _reactionAddEvent;
AsyncEventHandler<DiscordClient, MessageReactionAddEventArgs> _reactionAddHandler;
AsyncEvent<DiscordClient, MessageReactionRemoveEventArgs> _reactionRemoveEvent;
AsyncEventHandler<DiscordClient, MessageReactionRemoveEventArgs> _reactionRemoveHandler;
AsyncEvent<DiscordClient, MessageReactionsClearEventArgs> _reactionClearEvent;
AsyncEventHandler<DiscordClient, MessageReactionsClearEventArgs> _reactionClearHandler;
ConcurrentHashSet<ReactionCollectRequest> _requests;
/// <summary>
/// Creates a new EventWaiter object.
/// </summary>
/// <param name="client">Your DiscordClient</param>
public ReactionCollector(DiscordClient client)
{
this._client = client;
var tinfo = this._client.GetType().GetTypeInfo();
this._requests = new ConcurrentHashSet<ReactionCollectRequest>();
// Grabbing all three events from client
var handler = tinfo.DeclaredFields.First(x => x.FieldType == typeof(AsyncEvent<DiscordClient, MessageReactionAddEventArgs>));
this._reactionAddEvent = (AsyncEvent<DiscordClient, MessageReactionAddEventArgs>)handler.GetValue(this._client);
this._reactionAddHandler = new AsyncEventHandler<DiscordClient, MessageReactionAddEventArgs>(this.HandleReactionAdd);
this._reactionAddEvent.Register(this._reactionAddHandler);
handler = tinfo.DeclaredFields.First(x => x.FieldType == typeof(AsyncEvent<DiscordClient, MessageReactionRemoveEventArgs>));
this._reactionRemoveEvent = (AsyncEvent<DiscordClient, MessageReactionRemoveEventArgs>)handler.GetValue(this._client);
this._reactionRemoveHandler = new AsyncEventHandler<DiscordClient, MessageReactionRemoveEventArgs>(this.HandleReactionRemove);
this._reactionRemoveEvent.Register(this._reactionRemoveHandler);
handler = tinfo.DeclaredFields.First(x => x.FieldType == typeof(AsyncEvent<DiscordClient, MessageReactionsClearEventArgs>));
this._reactionClearEvent = (AsyncEvent<DiscordClient, MessageReactionsClearEventArgs>)handler.GetValue(this._client);
this._reactionClearHandler = new AsyncEventHandler<DiscordClient, MessageReactionsClearEventArgs>(this.HandleReactionClear);
this._reactionClearEvent.Register(this._reactionClearHandler);
}
/// <summary>
/// Collects the async.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>A Task.</returns>
public async Task<ReadOnlyCollection<Reaction>> CollectAsync(ReactionCollectRequest request)
{
this._requests.Add(request);
var result = (ReadOnlyCollection<Reaction>)null;
try
{
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<Reaction>(new HashSet<Reaction>(request.Collected).ToList());
request.Dispose();
this._requests.TryRemove(request);
}
return result;
}
/// <summary>
/// Handles the reaction add.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="eventArgs">The event's arguments.</param>
/// <returns>A Task.</returns>
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.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);
reaction.Users.Add(eventArgs.User);
req.Collected.Add(reaction);
}
else
{
req.Collected.Add(new Reaction()
{
Emoji = eventArgs.Emoji,
Users = new ConcurrentHashSet<DiscordUser>() { eventArgs.User }
});
}
}
}
return Task.CompletedTask;
}
/// <summary>
/// Handles the reaction remove.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="eventArgs">The event's arguments.</param>
/// <returns>A Task.</returns>
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.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);
reaction.Users.TryRemove(eventArgs.User);
if (reaction.Users.Count > 0)
req.Collected.Add(reaction);
}
}
}
return Task.CompletedTask;
}
/// <summary>
/// Handles the reaction clear.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="eventArgs">The event's arguments.</param>
/// <returns>A Task.</returns>
private Task HandleReactionClear(DiscordClient client, MessageReactionsClearEventArgs eventArgs)
{
// foreach request add
foreach (var req in this._requests)
{
if (req.Message.Id == eventArgs.Message.Id)
{
req.Collected.Clear();
}
}
return Task.CompletedTask;
}
~ReactionCollector()
{
this.Dispose();
}
/// <summary>
/// Disposes this EventWaiter
/// </summary>
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;
GC.SuppressFinalize(this);
}
}
/// <summary>
/// The reaction collect request.
/// </summary>
public class ReactionCollectRequest : IDisposable
{
internal TaskCompletionSource<Reaction> Tcs;
internal CancellationTokenSource Ct;
internal TimeSpan Timeout;
internal DiscordMessage Message;
internal ConcurrentHashSet<Reaction> Collected;
/// <summary>
/// Initializes a new instance of the <see cref="ReactionCollectRequest"/> class.
/// </summary>
/// <param name="msg">The msg.</param>
/// <param name="timeout">The timeout.</param>
public ReactionCollectRequest(DiscordMessage msg, TimeSpan timeout)
{
this.Message = msg;
this.Collected = new ConcurrentHashSet<Reaction>();
this.Timeout = timeout;
this.Tcs = new TaskCompletionSource<Reaction>();
this.Ct = new CancellationTokenSource(this.Timeout);
this.Ct.Token.Register(() => this.Tcs.TrySetResult(null));
}
~ReactionCollectRequest()
{
this.Dispose();
}
/// <summary>
/// Disposes the.
/// </summary>
public void Dispose()
{
GC.SuppressFinalize(this);
this.Ct.Dispose();
this.Tcs = null;
this.Message = null;
this.Collected?.Clear();
this.Collected = null;
}
}
/// <summary>
/// The reaction.
/// </summary>
public class Reaction
{
/// <summary>
/// Gets the emoji.
/// </summary>
public DiscordEmoji Emoji { get; internal set; }
/// <summary>
/// Gets the users.
/// </summary>
public ConcurrentHashSet<DiscordUser> Users { get; internal set; }
/// <summary>
/// Gets the total.
/// </summary>
public int Total => this.Users.Count;
}
diff --git a/DisCatSharp.Interactivity/EventHandling/Requests/CollectRequest.cs b/DisCatSharp.Interactivity/EventHandling/Requests/CollectRequest.cs
index 3023259db..b51aefadf 100644
--- a/DisCatSharp.Interactivity/EventHandling/Requests/CollectRequest.cs
+++ b/DisCatSharp.Interactivity/EventHandling/Requests/CollectRequest.cs
@@ -1,93 +1,93 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// CollectRequest is a class that serves as a representation of
/// EventArgs that are being collected within a specific time frame.
/// </summary>
/// <typeparam name="T"></typeparam>
internal class CollectRequest<T> : IDisposable where T : AsyncEventArgs
{
internal TaskCompletionSource<bool> Tcs;
internal CancellationTokenSource Ct;
internal Func<T, bool> Predicate;
internal TimeSpan Timeout;
internal ConcurrentHashSet<T> Collected;
/// <summary>
/// Creates a new CollectRequest object.
/// </summary>
/// <param name="predicate">Predicate to match</param>
/// <param name="timeout">Timeout time</param>
public CollectRequest(Func<T, bool> predicate, TimeSpan timeout)
{
this.Tcs = new TaskCompletionSource<bool>();
this.Ct = new CancellationTokenSource(timeout);
this.Predicate = predicate;
this.Ct.Token.Register(() => this.Tcs.TrySetResult(true));
this.Timeout = timeout;
this.Collected = new ConcurrentHashSet<T>();
}
~CollectRequest()
{
this.Dispose();
}
/// <summary>
/// Disposes this CollectRequest.
/// </summary>
public void Dispose()
{
this.Ct.Dispose();
this.Tcs = null;
this.Predicate = null;
if (this.Collected != null)
{
this.Collected.Clear();
this.Collected = null;
}
GC.SuppressFinalize(this);
}
}
/*
^ ^
( 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/IPaginationRequest.cs b/DisCatSharp.Interactivity/EventHandling/Requests/IPaginationRequest.cs
index ca1f8102a..fe205b640 100644
--- a/DisCatSharp.Interactivity/EventHandling/Requests/IPaginationRequest.cs
+++ b/DisCatSharp.Interactivity/EventHandling/Requests/IPaginationRequest.cs
@@ -1,106 +1,106 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Entities;
namespace DisCatSharp.Interactivity.EventHandling;
/// <summary>
/// The pagination request.
/// </summary>
public interface IPaginationRequest
{
/// <summary>
/// Returns the number of pages.
/// </summary>
/// <returns></returns>
int PageCount { get; }
/// <summary>
/// Returns the current page.
/// </summary>
/// <returns></returns>
Task<Page> GetPageAsync();
/// <summary>
/// Tells the request to set its index to the first page.
/// </summary>
/// <returns></returns>
Task SkipLeftAsync();
/// <summary>
/// Tells the request to set its index to the last page.
/// </summary>
/// <returns></returns>
Task SkipRightAsync();
/// <summary>
/// Tells the request to increase its index by one.
/// </summary>
/// <returns></returns>
Task NextPageAsync();
/// <summary>
/// Tells the request to decrease its index by one.
/// </summary>
/// <returns></returns>
Task PreviousPageAsync();
/// <summary>
/// Requests the message buttons from the pagination request.
/// </summary>
/// <returns>The buttons.</returns>
Task<IEnumerable<DiscordButtonComponent>> GetButtonsAsync();
/// <summary>
/// Requests message emojis from pagination request.
/// </summary>
/// <returns></returns>
Task<PaginationEmojis> GetEmojisAsync();
/// <summary>
/// Gets pagination message from this request.
/// </summary>
/// <returns></returns>
Task<DiscordMessage> GetMessageAsync();
/// <summary>
/// Gets the user this pagination applies to.
/// </summary>
/// <returns></returns>
Task<DiscordUser> GetUserAsync();
/// <summary>
/// Get this request's Task Completion Source.
/// </summary>
/// <returns></returns>
Task<TaskCompletionSource<bool>> GetTaskCompletionSourceAsync();
/// <summary>
/// Tells the request to perform cleanup.
/// </summary>
/// <returns></returns>
Task DoCleanupAsync();
}
diff --git a/DisCatSharp.Interactivity/EventHandling/Requests/MatchRequest.cs b/DisCatSharp.Interactivity/EventHandling/Requests/MatchRequest.cs
index 7161b2096..1f56b15cc 100644
--- a/DisCatSharp.Interactivity/EventHandling/Requests/MatchRequest.cs
+++ b/DisCatSharp.Interactivity/EventHandling/Requests/MatchRequest.cs
@@ -1,72 +1,72 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// MatchRequest is a class that serves as a representation of a
/// match that is being waited for.
/// </summary>
/// <typeparam name="T"></typeparam>
internal class MatchRequest<T> : IDisposable where T : AsyncEventArgs
{
internal TaskCompletionSource<T> Tcs;
internal CancellationTokenSource Ct;
internal Func<T, bool> Predicate;
internal TimeSpan Timeout;
/// <summary>
/// Creates a new MatchRequest object.
/// </summary>
/// <param name="predicate">Predicate to match</param>
/// <param name="timeout">Timeout time</param>
public MatchRequest(Func<T, bool> predicate, TimeSpan timeout)
{
this.Tcs = new TaskCompletionSource<T>();
this.Ct = new CancellationTokenSource(timeout);
this.Predicate = predicate;
this.Ct.Token.Register(() => this.Tcs.TrySetResult(null));
this.Timeout = timeout;
}
~MatchRequest()
{
this.Dispose();
}
/// <summary>
/// Disposes this MatchRequest.
/// </summary>
public void Dispose()
{
this.Ct.Dispose();
this.Tcs = null;
this.Predicate = null;
GC.SuppressFinalize(this);
}
}
diff --git a/DisCatSharp.Interactivity/EventHandling/Requests/PaginationRequest.cs b/DisCatSharp.Interactivity/EventHandling/Requests/PaginationRequest.cs
index 5cc48ff5a..6e9ecae0f 100644
--- a/DisCatSharp.Interactivity/EventHandling/Requests/PaginationRequest.cs
+++ b/DisCatSharp.Interactivity/EventHandling/Requests/PaginationRequest.cs
@@ -1,320 +1,320 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Interactivity.Enums;
namespace DisCatSharp.Interactivity.EventHandling
{
/// <summary>
/// The pagination request.
/// </summary>
internal class PaginationRequest : IPaginationRequest
{
private TaskCompletionSource<bool> _tcs;
private readonly CancellationTokenSource _ct;
private readonly TimeSpan _timeout;
private readonly List<Page> _pages;
private readonly PaginationBehaviour _behaviour;
private readonly DiscordMessage _message;
private readonly PaginationEmojis _emojis;
private readonly DiscordUser _user;
private int _index;
/// <summary>
/// Creates a new Pagination request
/// </summary>
/// <param name="message">Message to paginate</param>
/// <param name="user">User to allow control for</param>
/// <param name="behaviour">Behaviour during pagination</param>
/// <param name="deletion">Behavior on pagination end</param>
/// <param name="emojis">Emojis for this pagination object</param>
/// <param name="timeout">Timeout time</param>
/// <param name="pages">Pagination pages</param>
internal PaginationRequest(DiscordMessage message, DiscordUser user, PaginationBehaviour behaviour, PaginationDeletion deletion,
PaginationEmojis emojis, TimeSpan timeout, IEnumerable<Page> pages)
{
this._tcs = new TaskCompletionSource<bool>();
this._ct = new CancellationTokenSource(timeout);
this._ct.Token.Register(() => this._tcs.TrySetResult(true));
this._timeout = timeout;
this._message = message;
this._user = user;
this.PaginationDeletion = deletion;
this._behaviour = behaviour;
this._emojis = emojis;
this._pages = new List<Page>();
foreach (var p in pages)
{
this._pages.Add(p);
}
}
/// <summary>
/// Gets the page count.
/// </summary>
public int PageCount => this._pages.Count;
/// <summary>
/// Gets the pagination deletion.
/// </summary>
public PaginationDeletion PaginationDeletion { get; }
/// <summary>
/// Gets the page async.
/// </summary>
/// <returns>A Task.</returns>
public async Task<Page> GetPageAsync()
{
await Task.Yield();
return this._pages[this._index];
}
/// <summary>
/// Skips the left async.
/// </summary>
/// <returns>A Task.</returns>
public async Task SkipLeftAsync()
{
await Task.Yield();
this._index = 0;
}
/// <summary>
/// Skips the right async.
/// </summary>
/// <returns>A Task.</returns>
public async Task SkipRightAsync()
{
await Task.Yield();
this._index = this._pages.Count - 1;
}
/// <summary>
/// Nexts the page async.
/// </summary>
/// <returns>A Task.</returns>
public async Task NextPageAsync()
{
await Task.Yield();
switch (this._behaviour)
{
case PaginationBehaviour.Ignore:
if (this._index == this._pages.Count - 1)
break;
else
this._index++;
break;
case PaginationBehaviour.WrapAround:
if (this._index == this._pages.Count - 1)
this._index = 0;
else
this._index++;
break;
}
}
/// <summary>
/// Previous the page async.
/// </summary>
/// <returns>A Task.</returns>
public async Task PreviousPageAsync()
{
await Task.Yield();
switch (this._behaviour)
{
case PaginationBehaviour.Ignore:
if (this._index == 0)
break;
else
this._index--;
break;
case PaginationBehaviour.WrapAround:
if (this._index == 0)
this._index = this._pages.Count - 1;
else
this._index--;
break;
}
}
/// <summary>
/// Gets the buttons async.
/// </summary>
/// <returns><see cref="NotSupportedException"/></returns>
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
public async Task<IEnumerable<DiscordButtonComponent>> GetButtonsAsync()
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
=> throw new NotSupportedException("This request does not support buttons.");
/// <summary>
/// Gets the emojis async.
/// </summary>
/// <returns>A Task.</returns>
public async Task<PaginationEmojis> GetEmojisAsync()
{
await Task.Yield();
return this._emojis;
}
/// <summary>
/// Gets the message async.
/// </summary>
/// <returns>A Task.</returns>
public async Task<DiscordMessage> GetMessageAsync()
{
await Task.Yield();
return this._message;
}
/// <summary>
/// Gets the user async.
/// </summary>
/// <returns>A Task.</returns>
public async Task<DiscordUser> GetUserAsync()
{
await Task.Yield();
return this._user;
}
/// <summary>
/// Dos the cleanup async.
/// </summary>
/// <returns>A Task.</returns>
public async Task DoCleanupAsync()
{
switch (this.PaginationDeletion)
{
case PaginationDeletion.DeleteEmojis:
await this._message.DeleteAllReactionsAsync().ConfigureAwait(false);
break;
case PaginationDeletion.DeleteMessage:
await this._message.DeleteAsync().ConfigureAwait(false);
break;
case PaginationDeletion.KeepEmojis:
break;
}
}
/// <summary>
/// Gets the task completion source async.
/// </summary>
/// <returns>A Task.</returns>
public async Task<TaskCompletionSource<bool>> GetTaskCompletionSourceAsync()
{
await Task.Yield();
return this._tcs;
}
~PaginationRequest()
{
this.Dispose();
}
/// <summary>
/// Disposes this PaginationRequest.
/// </summary>
public void Dispose()
{
this._ct.Dispose();
this._tcs = null;
}
}
}
namespace DisCatSharp.Interactivity
{
/// <summary>
/// The pagination emojis.
/// </summary>
public class PaginationEmojis
{
public DiscordEmoji SkipLeft;
public DiscordEmoji SkipRight;
public DiscordEmoji Left;
public DiscordEmoji Right;
public DiscordEmoji Stop;
/// <summary>
/// Initializes a new instance of the <see cref="PaginationEmojis"/> class.
/// </summary>
public PaginationEmojis()
{
this.Left = DiscordEmoji.FromUnicode("◀");
this.Right = DiscordEmoji.FromUnicode("▶");
this.SkipLeft = DiscordEmoji.FromUnicode("⏮");
this.SkipRight = DiscordEmoji.FromUnicode("⏭");
this.Stop = DiscordEmoji.FromUnicode("⏹");
}
}
/// <summary>
/// The page.
/// </summary>
public class Page
{
/// <summary>
/// Gets or sets the content.
/// </summary>
public string Content { get; set; }
/// <summary>
/// Gets or sets the embed.
/// </summary>
public DiscordEmbed Embed { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="Page"/> class.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="embed">The embed.</param>
public Page(string content = "", DiscordEmbedBuilder embed = null)
{
this.Content = content;
this.Embed = embed?.Build();
}
}
}
diff --git a/DisCatSharp.Interactivity/EventHandling/Requests/PollRequest.cs b/DisCatSharp.Interactivity/EventHandling/Requests/PollRequest.cs
index d52b2106a..99032170b 100644
--- a/DisCatSharp.Interactivity/EventHandling/Requests/PollRequest.cs
+++ b/DisCatSharp.Interactivity/EventHandling/Requests/PollRequest.cs
@@ -1,155 +1,155 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// The poll request.
/// </summary>
public class PollRequest
{
internal TaskCompletionSource<bool> Tcs;
internal CancellationTokenSource Ct;
internal TimeSpan Timeout;
internal ConcurrentHashSet<PollEmoji> Collected;
internal DiscordMessage Message;
internal List<DiscordEmoji> Emojis;
/// <summary>
///
/// </summary>
/// <param name="message"></param>
/// <param name="timeout"></param>
/// <param name="emojis"></param>
public PollRequest(DiscordMessage message, TimeSpan timeout, IEnumerable<DiscordEmoji> emojis)
{
this.Tcs = new TaskCompletionSource<bool>();
this.Ct = new CancellationTokenSource(timeout);
this.Ct.Token.Register(() => this.Tcs.TrySetResult(true));
this.Timeout = timeout;
this.Emojis = emojis.ToList();
this.Collected = new ConcurrentHashSet<PollEmoji>();
this.Message = message;
foreach (var e in emojis)
{
this.Collected.Add(new PollEmoji(e));
}
}
/// <summary>
/// Clears the collected.
/// </summary>
internal void ClearCollected()
{
this.Collected.Clear();
foreach (var e in this.Emojis)
{
this.Collected.Add(new PollEmoji(e));
}
}
/// <summary>
/// Removes the reaction.
/// </summary>
/// <param name="emoji">The emoji.</param>
/// <param name="member">The member.</param>
internal void RemoveReaction(DiscordEmoji emoji, DiscordUser member)
{
if (this.Collected.Any(x => x.Emoji == emoji))
{
if (this.Collected.Any(x => x.Voted.Contains(member)))
{
var e = this.Collected.First(x => x.Emoji == emoji);
this.Collected.TryRemove(e);
e.Voted.TryRemove(member);
this.Collected.Add(e);
}
}
}
/// <summary>
/// Adds the reaction.
/// </summary>
/// <param name="emoji">The emoji.</param>
/// <param name="member">The member.</param>
internal void AddReaction(DiscordEmoji emoji, DiscordUser member)
{
if (this.Collected.Any(x => x.Emoji == emoji))
{
if (!this.Collected.Any(x => x.Voted.Contains(member)))
{
var e = this.Collected.First(x => x.Emoji == emoji);
this.Collected.TryRemove(e);
e.Voted.Add(member);
this.Collected.Add(e);
}
}
}
~PollRequest()
{
this.Dispose();
}
/// <summary>
/// Disposes this PollRequest.
/// </summary>
public void Dispose()
{
this.Ct.Dispose();
this.Tcs = null;
}
}
/// <summary>
/// The poll emoji.
/// </summary>
public class PollEmoji
{
/// <summary>
/// Initializes a new instance of the <see cref="PollEmoji"/> class.
/// </summary>
/// <param name="emoji">The emoji.</param>
internal PollEmoji(DiscordEmoji emoji)
{
this.Emoji = emoji;
this.Voted = new ConcurrentHashSet<DiscordUser>();
}
public DiscordEmoji Emoji;
public ConcurrentHashSet<DiscordUser> Voted;
/// <summary>
/// Gets the total.
/// </summary>
public int Total => this.Voted.Count;
}
diff --git a/DisCatSharp.Interactivity/Extensions/ChannelExtensions.cs b/DisCatSharp.Interactivity/Extensions/ChannelExtensions.cs
index 5710cd186..1597de849 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-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Interactivity extension methods for <see cref="DisCatSharp.Entities.DiscordChannel"/>.
/// </summary>
public static class ChannelExtensions
{
/// <summary>
/// Waits for the next message sent in this channel that satisfies the predicate.
/// </summary>
/// <param name="channel">The channel to monitor.</param>
/// <param name="predicate">A predicate that should return <see langword="true"/> if a message matches.</param>
/// <param name="timeoutOverride">Overrides the timeout set in <see cref="InteractivityConfiguration.Timeout"/></param>
/// <exception cref="InvalidOperationException">Thrown if interactivity is not enabled for the client associated with the channel.</exception>
public static Task<InteractivityResult<DiscordMessage>> GetNextMessageAsync(this DiscordChannel channel, Func<DiscordMessage, bool> predicate, TimeSpan? timeoutOverride = null)
=> GetInteractivity(channel).WaitForMessageAsync(msg => msg.ChannelId == channel.Id && predicate(msg), timeoutOverride);
/// <summary>
/// Waits for the next message sent in this channel.
/// </summary>
/// <param name="channel">The channel to monitor.</param>
/// <param name="timeoutOverride">Overrides the timeout set in <see cref="InteractivityConfiguration.Timeout"/></param>
/// <exception cref="InvalidOperationException">Thrown if interactivity is not enabled for the client associated with the channel.</exception>
public static Task<InteractivityResult<DiscordMessage>> GetNextMessageAsync(this DiscordChannel channel, TimeSpan? timeoutOverride = null)
=> channel.GetNextMessageAsync(msg => true, timeoutOverride);
/// <summary>
/// Waits for the next message sent in this channel from a specific user.
/// </summary>
/// <param name="channel">The channel to monitor.</param>
/// <param name="user">The target user.</param>
/// <param name="timeoutOverride">Overrides the timeout set in <see cref="InteractivityConfiguration.Timeout"/></param>
/// <exception cref="InvalidOperationException">Thrown if interactivity is not enabled for the client associated with the channel.</exception>
public static Task<InteractivityResult<DiscordMessage>> GetNextMessageAsync(this DiscordChannel channel, DiscordUser user, TimeSpan? timeoutOverride = null)
=> channel.GetNextMessageAsync(msg => msg.Author.Id == user.Id, timeoutOverride);
/// <summary>
/// Waits for a specific user to start typing in this channel.
/// </summary>
/// <param name="channel">The target channel.</param>
/// <param name="user">The target user.</param>
/// <param name="timeoutOverride">Overrides the timeout set in <see cref="InteractivityConfiguration.Timeout"/></param>
/// <exception cref="InvalidOperationException">Thrown if interactivity is not enabled for the client associated with the channel.</exception>
public static Task<InteractivityResult<TypingStartEventArgs>> WaitForUserTypingAsync(this DiscordChannel channel, DiscordUser user, TimeSpan? timeoutOverride = null)
=> GetInteractivity(channel).WaitForUserTypingAsync(user, channel, timeoutOverride);
/// <summary>
/// Sends a new paginated message.
/// </summary>
/// <param name="channel">Target channel.</param>
/// <param name="user">The user that will be able to control the pages.</param>
/// <param name="pages">A collection of <see cref="Page"/> to display.</param>
/// <param name="emojis">Pagination emojis.</param>
/// <param name="behaviour">Pagination behaviour (when hitting max and min indices).</param>
/// <param name="deletion">Deletion behaviour.</param>
/// <param name="timeoutOverride">Override timeout period.</param>
/// <exception cref="InvalidOperationException">Thrown if interactivity is not enabled for the client associated with the channel.</exception>
public static Task SendPaginatedMessageAsync(this DiscordChannel channel, DiscordUser user, IEnumerable<Page> pages, PaginationEmojis emojis, PaginationBehaviour? behaviour = default, PaginationDeletion? deletion = default, TimeSpan? timeoutOverride = null)
=> GetInteractivity(channel).SendPaginatedMessageAsync(channel, user, pages, emojis, behaviour, deletion, timeoutOverride);
/// <summary>
/// Sends a new paginated message with buttons.
/// </summary>
/// <param name="channel">Target channel.</param>
/// <param name="user">The user that will be able to control the pages.</param>
/// <param name="pages">A collection of <see cref="Page"/> to display.</param>
/// <param name="buttons">Pagination buttons (leave null to default to ones on configuration).</param>
/// <param name="behaviour">Pagination behaviour.</param>
/// <param name="deletion">Deletion behaviour</param>
/// <param name="token">A custom cancellation token that can be cancelled at any point.</param>
/// <exception cref="InvalidOperationException">Thrown if interactivity is not enabled for the client associated with the channel.</exception>
public static Task SendPaginatedMessageAsync(this DiscordChannel channel, DiscordUser user, IEnumerable<Page> pages, PaginationButtons buttons, PaginationBehaviour? behaviour = default, ButtonPaginationBehavior? deletion = default, CancellationToken token = default)
=> GetInteractivity(channel).SendPaginatedMessageAsync(channel, user, pages, buttons, behaviour, deletion, token);
/// <inheritdoc cref="SendPaginatedMessageAsync(DiscordChannel, DiscordUser, IEnumerable{Page}, PaginationButtons, PaginationBehaviour?, ButtonPaginationBehavior?, CancellationToken)"/>
public static Task SendPaginatedMessageAsync(this DiscordChannel channel, DiscordUser user, IEnumerable<Page> pages, PaginationBehaviour? behaviour = default, ButtonPaginationBehavior? deletion = default, CancellationToken token = default)
=> channel.SendPaginatedMessageAsync(user, pages, default, behaviour, deletion, token);
/// <summary>
/// Sends a new paginated message with buttons.
/// </summary>
/// <param name="channel">Target channel.</param>
/// <param name="user">The user that will be able to control the pages.</param>
/// <param name="pages">A collection of <see cref="Page"/> to display.</param>
/// <param name="buttons">Pagination buttons (leave null to default to ones on configuration).</param>
/// <param name="behaviour">Pagination behaviour.</param>
/// <param name="deletion">Deletion behaviour.</param>
/// <param name="timeoutOverride">Override timeout period.</param>
/// <exception cref="InvalidOperationException">Thrown if interactivity is not enabled for the client associated with the channel.</exception>
public static Task SendPaginatedMessageAsync(this DiscordChannel channel, DiscordUser user, IEnumerable<Page> pages, PaginationButtons buttons, TimeSpan? timeoutOverride, PaginationBehaviour? behaviour = default, ButtonPaginationBehavior? deletion = default)
=> GetInteractivity(channel).SendPaginatedMessageAsync(channel, user, pages, buttons, timeoutOverride, behaviour, deletion);
/// <summary>
/// Sends the paginated message async.
/// </summary>
/// <param name="channel">The channel.</param>
/// <param name="user">The user.</param>
/// <param name="pages">The pages.</param>
/// <param name="timeoutOverride">Override timeout period.</param>
/// <param name="behaviour">The behaviour.</param>
/// <param name="deletion">The deletion.</param>
/// <returns>A Task.</returns>
public static Task SendPaginatedMessageAsync(this DiscordChannel channel, DiscordUser user, IEnumerable<Page> pages, TimeSpan? timeoutOverride, PaginationBehaviour? behaviour = default, ButtonPaginationBehavior? deletion = default)
=> channel.SendPaginatedMessageAsync(user, pages, default, timeoutOverride, behaviour, deletion);
/// <summary>
/// Retrieves an interactivity instance from a channel instance.
/// </summary>
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")}.");
}
}
diff --git a/DisCatSharp.Interactivity/Extensions/ClientExtensions.cs b/DisCatSharp.Interactivity/Extensions/ClientExtensions.cs
index 80279d85e..4d2e5c31b 100644
--- a/DisCatSharp.Interactivity/Extensions/ClientExtensions.cs
+++ b/DisCatSharp.Interactivity/Extensions/ClientExtensions.cs
@@ -1,99 +1,99 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Interactivity extension methods for <see cref="DiscordClient"/> and <see cref="DiscordShardedClient"/>.
/// </summary>
public static class ClientExtensions
{
/// <summary>
/// Enables interactivity for this <see cref="DiscordClient"/> instance.
/// </summary>
/// <param name="client">The client to enable interactivity for.</param>
/// <param name="configuration">A configuration instance. Default configuration values will be used if none is provided.</param>
/// <returns>A brand new <see cref="InteractivityExtension"/> instance.</returns>
/// <exception cref="InvalidOperationException">Thrown if interactivity has already been enabled for the client instance.</exception>
public static InteractivityExtension UseInteractivity(this DiscordClient client, InteractivityConfiguration configuration = null)
{
if (client.GetExtension<InteractivityExtension>() != 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;
}
/// <summary>
/// Enables interactivity for each shard.
/// </summary>
/// <param name="client">The shard client to enable interactivity for.</param>
/// <param name="configuration">Configuration to use for all shards. If one isn't provided, default configuration values will be used.</param>
/// <returns>A dictionary containing new <see cref="InteractivityExtension"/> instances for each shard.</returns>
public static async Task<IReadOnlyDictionary<int, InteractivityExtension>> UseInteractivityAsync(this DiscordShardedClient client, InteractivityConfiguration configuration = null)
{
var extensions = new Dictionary<int, InteractivityExtension>();
await client.InitializeShardsAsync().ConfigureAwait(false);
foreach (var shard in client.ShardClients.Select(xkvp => xkvp.Value))
{
var extension = shard.GetExtension<InteractivityExtension>() ?? shard.UseInteractivity(configuration);
extensions.Add(shard.ShardId, extension);
}
return new ReadOnlyDictionary<int, InteractivityExtension>(extensions);
}
/// <summary>
/// Retrieves the registered <see cref="InteractivityExtension"/> instance for this client.
/// </summary>
/// <param name="client">The client to retrieve an <see cref="InteractivityExtension"/> instance from.</param>
/// <returns>An existing <see cref="InteractivityExtension"/> instance, or <see langword="null"/> if interactivity is not enabled for the <see cref="DiscordClient"/> instance.</returns>
public static InteractivityExtension GetInteractivity(this DiscordClient client)
=> client.GetExtension<InteractivityExtension>();
/// <summary>
/// Retrieves a <see cref="InteractivityExtension"/> instance for each shard.
/// </summary>
/// <param name="client">The shard client to retrieve interactivity instances from.</param>
/// <returns>A dictionary containing <see cref="InteractivityExtension"/> instances for each shard.</returns>
public static async Task<ReadOnlyDictionary<int, InteractivityExtension>> GetInteractivityAsync(this DiscordShardedClient client)
{
await client.InitializeShardsAsync().ConfigureAwait(false);
var extensions = new Dictionary<int, InteractivityExtension>();
foreach (var shard in client.ShardClients.Select(xkvp => xkvp.Value))
{
extensions.Add(shard.ShardId, shard.GetExtension<InteractivityExtension>());
}
return new ReadOnlyDictionary<int, InteractivityExtension>(extensions);
}
}
diff --git a/DisCatSharp.Interactivity/Extensions/InteractionExtensions.cs b/DisCatSharp.Interactivity/Extensions/InteractionExtensions.cs
index a51e786ed..9b84cf262 100644
--- a/DisCatSharp.Interactivity/Extensions/InteractionExtensions.cs
+++ b/DisCatSharp.Interactivity/Extensions/InteractionExtensions.cs
@@ -1,121 +1,121 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using DisCatSharp.Interactivity.Enums;
using DisCatSharp.Interactivity.EventHandling;
namespace DisCatSharp.Interactivity.Extensions;
/// <summary>
/// The interaction extensions.
/// </summary>
public static class InteractionExtensions
{
/// <summary>
/// Sends a paginated message in response to an interaction.
/// <para>
/// <b>Pass the interaction directly. Interactivity will ACK it.</b>
/// </para>
/// </summary>
/// <param name="interaction">The interaction to create a response to.</param>
/// <param name="deferred">Whether the interaction was deferred.</param>
/// <param name="ephemeral">Whether the response should be ephemeral.</param>
/// <param name="user">The user to listen for button presses from.</param>
/// <param name="pages">The pages to paginate.</param>
/// <param name="buttons">Optional: custom buttons</param>
/// <param name="behaviour">Pagination behaviour.</param>
/// <param name="deletion">Deletion behaviour</param>
/// <param name="token">A custom cancellation token that can be cancelled at any point.</param>
public static Task SendPaginatedResponseAsync(this DiscordInteraction interaction, bool deferred, bool ephemeral, DiscordUser user, IEnumerable<Page> pages, PaginationButtons buttons = null, PaginationBehaviour? behaviour = default, ButtonPaginationBehavior? deletion = default, CancellationToken token = default)
=> MessageExtensions.GetInteractivity(interaction.Message).SendPaginatedResponseAsync(interaction, deferred, ephemeral, user, pages, buttons, behaviour, deletion, token);
/// <summary>
/// Sends multiple modals to the user with a prompt to open the next one.
/// </summary>
/// <param name="interaction">The interaction to create a response to.</param>
/// <param name="modals">The modal pages.</param>
/// <param name="timeOutOverride">A custom timeout. (Default: 15 minutes)</param>
/// <returns>A read-only dictionary with the customid of the components as the key.</returns>
/// <exception cref="ArgumentException">Is thrown when no modals are defined.</exception>
/// <exception cref="InvalidOperationException">Is thrown when interactivity is not enabled for the client/shard.</exception>
public static async Task<PaginatedModalResponse> CreatePaginatedModalResponseAsync(this DiscordInteraction interaction, IReadOnlyList<ModalPage> modals, TimeSpan? timeOutOverride = null)
{
if (modals is null || modals.Count == 0)
throw new ArgumentException("You have to set at least one page");
var client = (DiscordClient)interaction.Discord;
var interactivity = client.GetInteractivity() ?? throw new InvalidOperationException($"Interactivity is not enabled for this {(client.IsShard ? "shard" : "client")}.");
timeOutOverride ??= TimeSpan.FromMinutes(15);
Dictionary<string, string> caughtResponses = new();
var previousInteraction = interaction;
foreach (var b in modals)
{
var modal = b.Modal.WithCustomId(Guid.NewGuid().ToString());
if (previousInteraction.Type is InteractionType.Ping or InteractionType.ModalSubmit)
{
await previousInteraction.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, b.OpenMessage.AddComponents(b.OpenButton));
var originalResponse = await previousInteraction.GetOriginalResponseAsync();
var modalOpen = await interactivity.WaitForButtonAsync(originalResponse, new List<DiscordButtonComponent> { b.OpenButton }, timeOutOverride);
if (modalOpen.TimedOut)
{
_ = previousInteraction.EditOriginalResponseAsync(new DiscordWebhookBuilder().WithContent(b.OpenMessage.Content).AddComponents(b.OpenButton.Disable()));
return new PaginatedModalResponse { TimedOut = true };
}
await modalOpen.Result.Interaction.CreateInteractionModalResponseAsync(modal);
}
else
{
await previousInteraction.CreateInteractionModalResponseAsync(modal);
}
_ = previousInteraction.EditOriginalResponseAsync(new DiscordWebhookBuilder().WithContent(b.OpenMessage.Content).AddComponents(b.OpenButton.Disable()));
var modalResult = await interactivity.WaitForModalAsync(modal.CustomId, timeOutOverride);
if (modalResult.TimedOut)
return new PaginatedModalResponse { TimedOut = true };
foreach (var submissions in modalResult.Result.Interaction.Data.Components)
caughtResponses.Add(submissions.CustomId, submissions.Value);
previousInteraction = modalResult.Result.Interaction;
}
await previousInteraction.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource, new DiscordInteractionResponseBuilder().AsEphemeral());
return new PaginatedModalResponse { TimedOut = false, Responses = caughtResponses, Interaction = previousInteraction };
}
}
diff --git a/DisCatSharp.Interactivity/Extensions/MessageExtensions.cs b/DisCatSharp.Interactivity/Extensions/MessageExtensions.cs
index 0cc9eea05..7888377b6 100644
--- a/DisCatSharp.Interactivity/Extensions/MessageExtensions.cs
+++ b/DisCatSharp.Interactivity/Extensions/MessageExtensions.cs
@@ -1,250 +1,250 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using DisCatSharp.EventArgs;
using DisCatSharp.Interactivity.Enums;
using DisCatSharp.Interactivity.EventHandling;
namespace DisCatSharp.Interactivity.Extensions;
/// <summary>
/// Interactivity extension methods for <see cref="DisCatSharp.Entities.DiscordMessage"/>.
/// </summary>
public static class MessageExtensions
{
/// <summary>
/// Waits for the next message that has the same author and channel as this message.
/// </summary>
/// <param name="message">Original message.</param>
/// <param name="timeoutOverride">Overrides the timeout set in <see cref="InteractivityConfiguration.Timeout"/></param>
public static Task<InteractivityResult<DiscordMessage>> GetNextMessageAsync(this DiscordMessage message, TimeSpan? timeoutOverride = null)
=> message.Channel.GetNextMessageAsync(message.Author, timeoutOverride);
/// <summary>
/// Waits for the next message with the same author and channel as this message, which also satisfies a predicate.
/// </summary>
/// <param name="message">Original message.</param>
/// <param name="predicate">A predicate that should return <see langword="true"/> if a message matches.</param>
/// <param name="timeoutOverride">Overrides the timeout set in <see cref="InteractivityConfiguration.Timeout"/></param>
public static Task<InteractivityResult<DiscordMessage>> GetNextMessageAsync(this DiscordMessage message, Func<DiscordMessage, bool> predicate, TimeSpan? timeoutOverride = null)
=> message.Channel.GetNextMessageAsync(msg => msg.Author.Id == message.Author.Id && message.ChannelId == msg.ChannelId && predicate(msg), timeoutOverride);
/// <summary>
/// Waits for any button to be pressed on the specified message.
/// </summary>
/// <param name="message">The message to wait on.</param>
public static Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForButtonAsync(this DiscordMessage message)
=> GetInteractivity(message).WaitForButtonAsync(message);
/// <summary>
/// Waits for any button to be pressed on the specified message.
/// </summary>
/// <param name="message">The message to wait on.</param>
/// <param name="timeoutOverride">Overrides the timeout set in <see cref="InteractivityConfiguration.Timeout"/></param>
public static Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForButtonAsync(this DiscordMessage message, TimeSpan? timeoutOverride = null)
=> GetInteractivity(message).WaitForButtonAsync(message, timeoutOverride);
/// <summary>
/// Waits for any button to be pressed on the specified message.
/// </summary>
/// <param name="message">The message to wait on.</param>
/// <param name="token">A custom cancellation token that can be cancelled at any point.</param>
public static Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForButtonAsync(this DiscordMessage message, CancellationToken token)
=> GetInteractivity(message).WaitForButtonAsync(message, token);
/// <summary>
/// Waits for a button with the specified Id to be pressed on the specified message.
/// </summary>
/// <param name="message">The message to wait on.</param>
/// <param name="id">The Id of the button to wait for.</param>
/// <param name="timeoutOverride">Overrides the timeout set in <see cref="InteractivityConfiguration.Timeout"/></param>
public static Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForButtonAsync(this DiscordMessage message, string id, TimeSpan? timeoutOverride = null)
=> GetInteractivity(message).WaitForButtonAsync(message, id, timeoutOverride);
/// <summary>
/// Waits for a button with the specified Id to be pressed on the specified message.
/// </summary>
/// <param name="message">The message to wait on.</param>
/// <param name="id">The Id of the button to wait for.</param>
/// <param name="token">A custom cancellation token that can be cancelled at any point.</param>
public static Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForButtonAsync(this DiscordMessage message, string id, CancellationToken token)
=> GetInteractivity(message).WaitForButtonAsync(message, id, token);
/// <summary>
/// Waits for any button to be pressed on the specified message by the specified user.
/// </summary>
/// <param name="message">The message to wait on.</param>
/// <param name="user">The user to wait for button input from.</param>
/// <param name="timeoutOverride">Overrides the timeout set in <see cref="InteractivityConfiguration.Timeout"/></param>
public static Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForButtonAsync(this DiscordMessage message, DiscordUser user, TimeSpan? timeoutOverride = null)
=> GetInteractivity(message).WaitForButtonAsync(message, user, timeoutOverride);
/// <summary>
/// Waits for any button to be pressed on the specified message by the specified user.
/// </summary>
/// <param name="message">The message to wait on.</param>
/// <param name="user">The user to wait for button input from.</param>
/// <param name="token">A custom cancellation token that can be cancelled at any point.</param>
public static Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForButtonAsync(this DiscordMessage message, DiscordUser user, CancellationToken token)
=> GetInteractivity(message).WaitForButtonAsync(message, user, token);
/// <summary>
/// Waits for any button to be interacted with.
/// </summary>
/// <param name="message">The message to wait on.</param>
/// <param name="predicate">The predicate to filter interactions by.</param>
/// <param name="timeoutOverride">Override the timeout specified in <see cref="InteractivityConfiguration"/></param>
public static Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForButtonAsync(this DiscordMessage message, Func<ComponentInteractionCreateEventArgs, bool> predicate, TimeSpan? timeoutOverride = null)
=> GetInteractivity(message).WaitForButtonAsync(message, predicate, timeoutOverride);
/// <summary>
/// Waits for any button to be interacted with.
/// </summary>
/// <param name="message">The message to wait on.</param>
/// <param name="predicate">The predicate to filter interactions by.</param>
/// <param name="token">A token to cancel interactivity with at any time. Pass <see cref="CancellationToken.None"/> to wait indefinitely.</param>
public static Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForButtonAsync(this DiscordMessage message, Func<ComponentInteractionCreateEventArgs, bool> predicate, CancellationToken token)
=> GetInteractivity(message).WaitForButtonAsync(message, predicate, token);
/// <summary>
/// Waits for any dropdown to be interacted with.
/// </summary>
/// <param name="message">The message to wait for.</param>
/// <param name="predicate">A filter predicate.</param>
/// <param name="selectType">The <see cref="ComponentType">type</see> of the select menu.</param>
/// <param name="timeoutOverride">Override the timeout period specified in <see cref="InteractivityConfiguration"/>.</param>
/// <exception cref="ArgumentException">Thrown when the message doesn't contain any dropdowns</exception>
public static Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForSelectAsync(this DiscordMessage message, Func<ComponentInteractionCreateEventArgs, bool> predicate, ComponentType selectType, TimeSpan? timeoutOverride = null)
=> GetInteractivity(message).WaitForSelectAsync(message, predicate, selectType, timeoutOverride);
/// <summary>
/// Waits for any dropdown to be interacted with.
/// </summary>
/// <param name="message">The message to wait for.</param>
/// <param name="predicate">A filter predicate.</param>
/// <param name="selectType">The <see cref="ComponentType">type</see> of the select menu.</param>
/// <param name="token">A token that can be used to cancel interactivity. Pass <see cref="CancellationToken.None"/> to wait indefinitely.</param>
/// <exception cref="ArgumentException">Thrown when the message doesn't contain any dropdowns</exception>
public static Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForSelectAsync(this DiscordMessage message, Func<ComponentInteractionCreateEventArgs, bool> predicate, ComponentType selectType, CancellationToken token)
=> GetInteractivity(message).WaitForSelectAsync(message, predicate, selectType, token);
/// <summary>
/// Waits for a dropdown to be interacted with.
/// </summary>
/// <param name="message">The message to wait on.</param>
/// <param name="id">The Id of the dropdown to wait for.</param>
/// <param name="selectType">The <see cref="ComponentType">type</see> of the select menu.</param>
/// <param name="timeoutOverride">Overrides the timeout set in <see cref="InteractivityConfiguration.Timeout"/></param>
public static Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForSelectAsync(this DiscordMessage message, string id, ComponentType selectType, TimeSpan? timeoutOverride = null)
=> GetInteractivity(message).WaitForSelectAsync(message, id, selectType, timeoutOverride);
/// <summary>
/// Waits for a dropdown to be interacted with.
/// </summary>
/// <param name="message">The message to wait on.</param>
/// <param name="id">The Id of the dropdown to wait for.</param>
/// <param name="selectType">The <see cref="ComponentType">type</see> of the select menu.</param>
/// <param name="token">A custom cancellation token that can be cancelled at any point.</param>
public static Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForSelectAsync(this DiscordMessage message, string id, ComponentType selectType, CancellationToken token)
=> GetInteractivity(message).WaitForSelectAsync(message, id, selectType, token);
/// <summary>
/// Waits for a dropdown to be interacted with by the specified user.
/// </summary>
/// <param name="message">The message to wait on.</param>
/// <param name="user">The user to wait for.</param>
/// <param name="id">The Id of the dropdown to wait for.</param>
/// <param name="selectType">The <see cref="ComponentType">type</see> of the select menu.</param>
/// <param name="timeoutOverride"></param>
public static Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForSelectAsync(this DiscordMessage message, DiscordUser user, string id, ComponentType selectType, TimeSpan? timeoutOverride = null)
=> GetInteractivity(message).WaitForSelectAsync(message, user, id, selectType, timeoutOverride);
/// <summary>
/// Waits for a dropdown to be interacted with by the specified user.
/// </summary>
/// <param name="message">The message to wait on.</param>
/// <param name="user">The user to wait for.</param>
/// <param name="id">The Id of the dropdown to wait for.</param>
/// <param name="selectType">The <see cref="ComponentType">type</see> of the select menu.</param>
/// <param name="token">A custom cancellation token that can be cancelled at any point.</param>
public static Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForSelectAsync(this DiscordMessage message, DiscordUser user, string id, ComponentType selectType, CancellationToken token)
=> GetInteractivity(message).WaitForSelectAsync(message, user, id, selectType, token);
/// <summary>
/// Waits for a reaction on this message from a specific user.
/// </summary>
/// <param name="message">Target message.</param>
/// <param name="user">The target user.</param>
/// <param name="timeoutOverride">Overrides the timeout set in <see cref="InteractivityConfiguration.Timeout"/></param>
/// <exception cref="InvalidOperationException">Thrown if interactivity is not enabled for the client associated with the message.</exception>
public static Task<InteractivityResult<MessageReactionAddEventArgs>> WaitForReactionAsync(this DiscordMessage message, DiscordUser user, TimeSpan? timeoutOverride = null)
=> GetInteractivity(message).WaitForReactionAsync(message, user, timeoutOverride);
/// <summary>
/// Waits for a specific reaction on this message from the specified user.
/// </summary>
/// <param name="message">Target message.</param>
/// <param name="user">The target user.</param>
/// <param name="emoji">The target emoji.</param>
/// <param name="timeoutOverride">Overrides the timeout set in <see cref="InteractivityConfiguration.Timeout"/></param>
/// <exception cref="InvalidOperationException">Thrown if interactivity is not enabled for the client associated with the message.</exception>
public static Task<InteractivityResult<MessageReactionAddEventArgs>> WaitForReactionAsync(this DiscordMessage message, DiscordUser user, DiscordEmoji emoji, TimeSpan? timeoutOverride = null)
=> GetInteractivity(message).WaitForReactionAsync(e => e.Emoji == emoji, message, user, timeoutOverride);
/// <summary>
/// Collects all reactions on this message within the timeout duration.
/// </summary>
/// <param name="message">The message to collect reactions from.</param>
/// <param name="timeoutOverride">Overrides the timeout set in <see cref="InteractivityConfiguration.Timeout"/></param>
/// <exception cref="InvalidOperationException">Thrown if interactivity is not enabled for the client associated with the message.</exception>
public static Task<ReadOnlyCollection<Reaction>> CollectReactionsAsync(this DiscordMessage message, TimeSpan? timeoutOverride = null)
=> GetInteractivity(message).CollectReactionsAsync(message, timeoutOverride);
/// <summary>
/// Begins a poll using this message.
/// </summary>
/// <param name="message">Target message.</param>
/// <param name="emojis">Options for this poll.</param>
/// <param name="behaviorOverride">Overrides the action set in <see cref="InteractivityConfiguration.PaginationBehaviour"/></param>
/// <param name="timeoutOverride">Overrides the timeout set in <see cref="InteractivityConfiguration.Timeout"/></param>
/// <exception cref="InvalidOperationException">Thrown if interactivity is not enabled for the client associated with the message.</exception>
public static Task<ReadOnlyCollection<PollEmoji>> DoPollAsync(this DiscordMessage message, IEnumerable<DiscordEmoji> emojis, PollBehaviour? behaviorOverride = null, TimeSpan? timeoutOverride = null)
=> GetInteractivity(message).DoPollAsync(message, emojis, behaviorOverride, timeoutOverride);
/// <summary>
/// Retrieves an interactivity instance from a message instance.
/// </summary>
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")}.");
}
}
diff --git a/DisCatSharp.Interactivity/GlobalSuppressions.cs b/DisCatSharp.Interactivity/GlobalSuppressions.cs
index 386c3f81b..b165a691d 100644
--- a/DisCatSharp.Interactivity/GlobalSuppressions.cs
+++ b/DisCatSharp.Interactivity/GlobalSuppressions.cs
@@ -1,33 +1,33 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Interactivity.InteractivityExtension.HandleInvalidInteraction(DisCatSharp.Entities.DiscordInteraction)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "<Pending>", Scope = "member", Target = "~F:DisCatSharp.Interactivity.EventHandling.PaginationRequest._timeout")]
[assembly: SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "<Pending>", Scope = "member", Target = "~F:DisCatSharp.Interactivity.InteractivityExtension._componentInteractionWaiter")]
[assembly: SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "<Pending>", Scope = "member", Target = "~F:DisCatSharp.Interactivity.InteractivityExtension._modalInteractionWaiter")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Interactivity.EventHandling.EventWaiter`1.CollectMatchesAsync(DisCatSharp.Interactivity.EventHandling.CollectRequest{`0})~System.Threading.Tasks.Task{System.Collections.ObjectModel.ReadOnlyCollection{`0}}")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Interactivity.EventHandling.EventWaiter`1.WaitForMatchAsync(DisCatSharp.Interactivity.EventHandling.MatchRequest{`0})~System.Threading.Tasks.Task{`0}")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Interactivity.InteractivityExtension.SplitString(System.String,System.Int32)~System.Collections.Generic.List{System.String}")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Interactivity.EventHandling.Paginator.PaginateAsync(DisCatSharp.Interactivity.EventHandling.IPaginationRequest,DisCatSharp.Entities.DiscordEmoji)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Interactivity.EventHandling.Paginator.ResetReactionsAsync(DisCatSharp.Interactivity.EventHandling.IPaginationRequest)~System.Threading.Tasks.Task")]
diff --git a/DisCatSharp.Interactivity/Helpers/InteractivityHelpers.cs b/DisCatSharp.Interactivity/Helpers/InteractivityHelpers.cs
index b38a493bb..5fcce2888 100644
--- a/DisCatSharp.Interactivity/Helpers/InteractivityHelpers.cs
+++ b/DisCatSharp.Interactivity/Helpers/InteractivityHelpers.cs
@@ -1,46 +1,46 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Interactivity
{
public static class InteractivityHelpers
{
public static List<Page> Recalculate(this List<Page> pages)
{
List<Page> recalulatedPages = new(pages.Count);
int pageCount = 1;
foreach (var page in pages)
{
var tempPage = new Page();
var replaceEmbed = new DiscordEmbedBuilder(page.Embed).WithFooter($"Page {pageCount}/{pages.Count}");
tempPage.Embed = replaceEmbed.Build();
recalulatedPages.Add(tempPage);
pageCount++;
}
return recalulatedPages;
}
}
}
diff --git a/DisCatSharp.Interactivity/InteractivityConfiguration.cs b/DisCatSharp.Interactivity/InteractivityConfiguration.cs
index 87df3cb46..7e237bbde 100644
--- a/DisCatSharp.Interactivity/InteractivityConfiguration.cs
+++ b/DisCatSharp.Interactivity/InteractivityConfiguration.cs
@@ -1,116 +1,116 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Interactivity.Enums;
using DisCatSharp.Interactivity.EventHandling;
using Microsoft.Extensions.DependencyInjection;
namespace DisCatSharp.Interactivity;
/// <summary>
/// Configuration class for your Interactivity extension
/// </summary>
public sealed class InteractivityConfiguration
{
/// <summary>
/// <para>Sets the default interactivity action timeout.</para>
/// <para>Defaults to 1 minute.</para>
/// </summary>
public TimeSpan Timeout { internal get; set; } = TimeSpan.FromMinutes(1);
/// <summary>
/// What to do after the poll ends
/// </summary>
public PollBehaviour PollBehaviour { internal get; set; } = PollBehaviour.DeleteEmojis;
/// <summary>
/// Emojis to use for pagination
/// </summary>
public PaginationEmojis PaginationEmojis { internal get; set; } = new();
/// <summary>
/// Buttons to use for pagination.
/// </summary>
public PaginationButtons PaginationButtons { internal get; set; } = new();
/// <summary>
/// Whether interactivity should ACK buttons that are pushed. Setting this to <see langword="true"/> will also prevent subsequent event handlers from running.
/// </summary>
public bool AckPaginationButtons { internal get; set; }
/// <summary>
/// How to handle buttons after pagination ends.
/// </summary>
public ButtonPaginationBehavior ButtonBehavior { internal get; set; }
/// <summary>
/// How to handle pagination. Defaults to WrapAround.
/// </summary>
public PaginationBehaviour PaginationBehaviour { internal get; set; } = PaginationBehaviour.WrapAround;
/// <summary>
/// How to handle pagination deletion. Defaults to DeleteEmojis.
/// </summary>
public PaginationDeletion PaginationDeletion { internal get; set; } = PaginationDeletion.DeleteEmojis;
/// <summary>
/// How to handle invalid interactions. Defaults to Ignore.
/// </summary>
public InteractionResponseBehavior ResponseBehavior { internal get; set; } = InteractionResponseBehavior.Ignore;
/// <summary>
/// The message to send to the user when processing invalid interactions. Ignored if <see cref="ResponseBehavior"/> is not set to <see cref="DisCatSharp.Interactivity.Enums.InteractionResponseBehavior.Respond"/>.
/// </summary>
public string ResponseMessage { internal get; set; }
/// <summary>
/// Creates a new instance of <see cref="InteractivityConfiguration"/>.
/// </summary>
[ActivatorUtilitiesConstructor]
public InteractivityConfiguration()
{
}
/// <summary>
/// Creates a new instance of <see cref="InteractivityConfiguration"/>, copying the properties of another configuration.
/// </summary>
/// <param name="other">Configuration the properties of which are to be copied.</param>
public InteractivityConfiguration(InteractivityConfiguration other)
{
this.AckPaginationButtons = other.AckPaginationButtons;
this.PaginationButtons = other.PaginationButtons;
this.ButtonBehavior = other.ButtonBehavior;
this.PaginationBehaviour = other.PaginationBehaviour;
this.PaginationDeletion = other.PaginationDeletion;
this.ResponseBehavior = other.ResponseBehavior;
this.PaginationEmojis = other.PaginationEmojis;
this.ResponseMessage = other.ResponseMessage;
this.PollBehaviour = other.PollBehaviour;
this.Timeout = other.Timeout;
if (this.ResponseBehavior is InteractionResponseBehavior.Respond && string.IsNullOrWhiteSpace(this.ResponseMessage))
throw new ArgumentException($"{nameof(this.ResponseMessage)} cannot be null, empty, or whitespace when {nameof(this.ResponseBehavior)} is set to respond.");
}
}
diff --git a/DisCatSharp.Interactivity/InteractivityEvents.cs b/DisCatSharp.Interactivity/InteractivityEvents.cs
index 51abdfabd..f448e406a 100644
--- a/DisCatSharp.Interactivity/InteractivityEvents.cs
+++ b/DisCatSharp.Interactivity/InteractivityEvents.cs
@@ -1,56 +1,56 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 Microsoft.Extensions.Logging;
namespace DisCatSharp.Interactivity;
/// <summary>
/// Contains well-defined event IDs used by the Interactivity extension.
/// </summary>
public static class InteractivityEvents
{
/// <summary>
/// Miscellaneous events, that do not fit in any other category.
/// </summary>
public static EventId Misc { get; } = new(500, "Interactivity");
/// <summary>
/// Events pertaining to errors that happen during waiting for events.
/// </summary>
public static EventId InteractivityWaitError { get; } = new(501, nameof(InteractivityWaitError));
/// <summary>
/// Events pertaining to pagination.
/// </summary>
public static EventId InteractivityPaginationError { get; } = new(502, nameof(InteractivityPaginationError));
/// <summary>
/// Events pertaining to polling.
/// </summary>
public static EventId InteractivityPollError { get; } = new(503, nameof(InteractivityPollError));
/// <summary>
/// Events pertaining to event collection.
/// </summary>
public static EventId InteractivityCollectorError { get; } = new(504, nameof(InteractivityCollectorError));
}
diff --git a/DisCatSharp.Interactivity/InteractivityExtension.cs b/DisCatSharp.Interactivity/InteractivityExtension.cs
index 0917db13d..61c848dc1 100644
--- a/DisCatSharp.Interactivity/InteractivityExtension.cs
+++ b/DisCatSharp.Interactivity/InteractivityExtension.cs
@@ -1,977 +1,977 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Extension class for DisCatSharp.Interactivity
/// </summary>
public class InteractivityExtension : BaseExtension
{
/// <summary>
/// Gets the config.
/// </summary>
internal InteractivityConfiguration Config { get; }
private EventWaiter<MessageCreateEventArgs> _messageCreatedWaiter;
private EventWaiter<MessageReactionAddEventArgs> _messageReactionAddWaiter;
private EventWaiter<TypingStartEventArgs> _typingStartWaiter;
private EventWaiter<ComponentInteractionCreateEventArgs> _modalInteractionWaiter;
private EventWaiter<ComponentInteractionCreateEventArgs> _componentInteractionWaiter;
private ComponentEventWaiter _componentEventWaiter;
private ModalEventWaiter _modalEventWaiter;
private ReactionCollector _reactionCollector;
private Poller _poller;
private Paginator _paginator;
private ComponentPaginator _compPaginator;
/// <summary>
/// Initializes a new instance of the <see cref="InteractivityExtension"/> class.
/// </summary>
/// <param name="cfg">The configuration.</param>
internal InteractivityExtension(InteractivityConfiguration cfg)
{
this.Config = new InteractivityConfiguration(cfg);
}
/// <summary>
/// Setups the Interactivity Extension.
/// </summary>
/// <param name="client">Discord client.</param>
protected internal override void Setup(DiscordClient client)
{
this.Client = client;
this._messageCreatedWaiter = new EventWaiter<MessageCreateEventArgs>(this.Client);
this._messageReactionAddWaiter = new EventWaiter<MessageReactionAddEventArgs>(this.Client);
this._componentInteractionWaiter = new EventWaiter<ComponentInteractionCreateEventArgs>(this.Client);
this._modalInteractionWaiter = new EventWaiter<ComponentInteractionCreateEventArgs>(this.Client);
this._typingStartWaiter = new EventWaiter<TypingStartEventArgs>(this.Client);
this._poller = new Poller(this.Client);
this._reactionCollector = new ReactionCollector(this.Client);
this._paginator = new Paginator(this.Client);
this._compPaginator = new ComponentPaginator(this.Client, this.Config);
this._componentEventWaiter = new ComponentEventWaiter(this.Client, this.Config);
this._modalEventWaiter = new ModalEventWaiter(this.Client, this.Config);
}
/// <summary>
/// Makes a poll and returns poll results.
/// </summary>
/// <param name="m">Message to create poll on.</param>
/// <param name="emojis">Emojis to use for this poll.</param>
/// <param name="behaviour">What to do when the poll ends.</param>
/// <param name="timeout">Override timeout period.</param>
/// <returns></returns>
public async Task<ReadOnlyCollection<PollEmoji>> DoPollAsync(DiscordMessage m, IEnumerable<DiscordEmoji> 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 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<PollEmoji>(res.ToList());
}
/// <summary>
/// Waits for any button in the specified collection to be pressed.
/// </summary>
/// <param name="message">The message to wait on.</param>
/// <param name="buttons">A collection of buttons to listen for.</param>
/// <param name="timeoutOverride">Override the timeout period in <see cref="InteractivityConfiguration"/>.</param>
/// <returns>A <see cref="InteractivityResult{T}"/> with the result of button that was pressed, if any.</returns>
/// <exception cref="InvalidOperationException">Thrown when attempting to wait for a message that is not authored by the current user.</exception>
/// <exception cref="ArgumentException">Thrown when the message does not contain a button with the specified Id, or any buttons at all.</exception>
public Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForButtonAsync(DiscordMessage message, IEnumerable<DiscordButtonComponent> buttons, TimeSpan? timeoutOverride = null)
=> this.WaitForButtonAsync(message, buttons, this.GetCancellationToken(timeoutOverride));
/// <summary>
/// Waits for any button in the specified collection to be pressed.
/// </summary>
/// <param name="message">The message to wait on.</param>
/// <param name="buttons">A collection of buttons to listen for.</param>
/// <param name="token">A custom cancellation token that can be cancelled at any point.</param>
/// <returns>A <see cref="InteractivityResult{T}"/> with the result of button that was pressed, if any.</returns>
/// <exception cref="InvalidOperationException">Thrown when attempting to wait for a message that is not authored by the current user.</exception>
/// <exception cref="ArgumentException">Thrown when the message does not contain a button with the specified Id, or any buttons at all.</exception>
public async Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForButtonAsync(DiscordMessage message, IEnumerable<DiscordButtonComponent> 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
.WaitForMatchAsync(new ComponentMatchRequest(message,
c =>
c.Interaction.Data.ComponentType == ComponentType.Button &&
buttons.Any(b => b.CustomId == c.Id), token)).ConfigureAwait(false);
return new InteractivityResult<ComponentInteractionCreateEventArgs>(res is null, res);
}
/// <summary>
/// Waits for a user modal submit.
/// </summary>
/// <param name="customId">The custom id of the modal to wait for.</param>
/// <param name="timeoutOverride">Override the timeout period specified in <see cref="InteractivityConfiguration"/>.</param>
/// <returns>A <see cref="InteractivityResult{T}"/> with the result of the modal.</returns>
public Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForModalAsync(string customId, TimeSpan? timeoutOverride = null)
=> this.WaitForModalAsync(customId, this.GetCancellationToken(timeoutOverride));
/// <summary>
/// Waits for a user modal submit.
/// </summary>
/// <param name="customId">The custom id of the modal to wait for.</param>
/// <param name="token">A custom cancellation token that can be cancelled at any point.</param>
/// <returns>A <see cref="InteractivityResult{T}"/> with the result of the modal.</returns>
public async Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForModalAsync(string customId, CancellationToken token)
{
var result =
await this
._modalEventWaiter
.WaitForModalMatchAsync(new ModalMatchRequest(customId, c => c.Interaction.Type == InteractionType.ModalSubmit, token))
.ConfigureAwait(false);
return new InteractivityResult<ComponentInteractionCreateEventArgs>(result is null, result);
}
/// <summary>
/// Waits for any button on the specified message to be pressed.
/// </summary>
/// <param name="message">The message to wait for the button on.</param>
/// <param name="timeoutOverride">Override the timeout period specified in <see cref="InteractivityConfiguration"/>.</param>
/// <returns>A <see cref="InteractivityResult{T}"/> with the result of button that was pressed, if any.</returns>
/// <exception cref="InvalidOperationException">Thrown when attempting to wait for a message that is not authored by the current user.</exception>
/// <exception cref="ArgumentException">Thrown when the message does not contain a button with the specified Id, or any buttons at all.</exception>
public Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForButtonAsync(DiscordMessage message, TimeSpan? timeoutOverride = null)
=> this.WaitForButtonAsync(message, this.GetCancellationToken(timeoutOverride));
/// <summary>
/// Waits for any button on the specified message to be pressed.
/// </summary>
/// <param name="message">The message to wait for the button on.</param>
/// <param name="token">A custom cancellation token that can be cancelled at any point.</param>
/// <returns>A <see cref="InteractivityResult{T}"/> with the result of button that was pressed, if any.</returns>
/// <exception cref="InvalidOperationException">Thrown when attempting to wait for a message that is not authored by the current user.</exception>
/// <exception cref="ArgumentException">Thrown when the message does not contain a button with the specified Id, or any buttons at all.</exception>
public async Task<InteractivityResult<ComponentInteractionCreateEventArgs>> 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
.WaitForMatchAsync(new ComponentMatchRequest(message, c => c.Interaction.Data.ComponentType == ComponentType.Button && ids.Contains(c.Id), token))
.ConfigureAwait(false);
return new InteractivityResult<ComponentInteractionCreateEventArgs>(result is null, result);
}
/// <summary>
/// Waits for any button on the specified message to be pressed by the specified user.
/// </summary>
/// <param name="message">The message to wait for the button on.</param>
/// <param name="user">The user to wait for the button press from.</param>
/// <param name="timeoutOverride">Override the timeout period specified in <see cref="InteractivityConfiguration"/>.</param>
/// <returns>A <see cref="InteractivityResult{T}"/> with the result of button that was pressed, if any.</returns>
/// <exception cref="InvalidOperationException">Thrown when attempting to wait for a message that is not authored by the current user.</exception>
/// <exception cref="ArgumentException">Thrown when the message does not contain a button with the specified Id, or any buttons at all.</exception>
public Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForButtonAsync(DiscordMessage message, DiscordUser user, TimeSpan? timeoutOverride = null)
=> this.WaitForButtonAsync(message, user, this.GetCancellationToken(timeoutOverride));
/// <summary>
/// Waits for any button on the specified message to be pressed by the specified user.
/// </summary>
/// <param name="message">The message to wait for the button on.</param>
/// <param name="user">The user to wait for the button press from.</param>
/// <param name="token">A custom cancellation token that can be cancelled at any point.</param>
/// <returns>A <see cref="InteractivityResult{T}"/> with the result of button that was pressed, if any.</returns>
/// <exception cref="InvalidOperationException">Thrown when attempting to wait for a message that is not authored by the current user.</exception>
/// <exception cref="ArgumentException">Thrown when the message does not contain a button with the specified Id, or any buttons at all.</exception>
public async Task<InteractivityResult<ComponentInteractionCreateEventArgs>> 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
.WaitForMatchAsync(new ComponentMatchRequest(message, (c) => c.Interaction.Data.ComponentType is ComponentType.Button && c.User == user, token))
.ConfigureAwait(false);
return new InteractivityResult<ComponentInteractionCreateEventArgs>(result is null, result);
}
/// <summary>
/// Waits for a button with the specified Id to be pressed.
/// </summary>
/// <param name="message">The message to wait for the button on.</param>
/// <param name="id">The Id of the button to wait for.</param>
/// <param name="timeoutOverride">Override the timeout period specified in <see cref="InteractivityConfiguration"/>.</param>
/// <returns>A <see cref="InteractivityResult{T}"/> with the result of the operation.</returns>
/// <exception cref="InvalidOperationException">Thrown when attempting to wait for a message that is not authored by the current user.</exception>
/// <exception cref="ArgumentException">Thrown when the message does not contain a button with the specified Id, or any buttons at all.</exception>
public Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForButtonAsync(DiscordMessage message, string id, TimeSpan? timeoutOverride = null)
=> this.WaitForButtonAsync(message, id, this.GetCancellationToken(timeoutOverride));
/// <summary>
/// Waits for a button with the specified Id to be pressed.
/// </summary>
/// <param name="message">The message to wait for the button on.</param>
/// <param name="id">The Id of the button to wait for.</param>
/// <param name="token">Override the timeout period specified in <see cref="InteractivityConfiguration"/>.</param>
/// <returns>A <see cref="InteractivityResult{T}"/> with the result of the operation.</returns>
/// <exception cref="InvalidOperationException">Thrown when attempting to wait for a message that is not authored by the current user.</exception>
/// <exception cref="ArgumentException">Thrown when the message does not contain a button with the specified Id, or any buttons at all.</exception>
public async Task<InteractivityResult<ComponentInteractionCreateEventArgs>> 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<DiscordButtonComponent>().Any(c => c.CustomId == id))
throw new ArgumentException($"Message does not contain button with Id of '{id}'.");
var result = await this
._componentEventWaiter
.WaitForMatchAsync(new ComponentMatchRequest(message, (c) => c.Interaction.Data.ComponentType is ComponentType.Button && c.Id == id, token))
.ConfigureAwait(false);
return new InteractivityResult<ComponentInteractionCreateEventArgs>(result is null, result);
}
/// <summary>
/// Waits for any button to be interacted with.
/// </summary>
/// <param name="message">The message to wait on.</param>
/// <param name="predicate">The predicate to filter interactions by.</param>
/// <param name="timeoutOverride">Override the timeout specified in <see cref="InteractivityConfiguration"/></param>
public Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForButtonAsync(DiscordMessage message, Func<ComponentInteractionCreateEventArgs, bool> predicate, TimeSpan? timeoutOverride = null)
=> this.WaitForButtonAsync(message, predicate, this.GetCancellationToken(timeoutOverride));
/// <summary>
/// Waits for any button to be interacted with.
/// </summary>
/// <param name="message">The message to wait on.</param>
/// <param name="predicate">The predicate to filter interactions by.</param>
/// <param name="token">A token to cancel interactivity with at any time. Pass <see cref="CancellationToken.None"/> to wait indefinitely.</param>
public async Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForButtonAsync(DiscordMessage message, Func<ComponentInteractionCreateEventArgs, bool> 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
.WaitForMatchAsync(new ComponentMatchRequest(message, c => c.Interaction.Data.ComponentType is ComponentType.Button && predicate(c), token))
.ConfigureAwait(false);
return new InteractivityResult<ComponentInteractionCreateEventArgs>(result is null, result);
}
/// <summary>
/// Waits for any dropdown to be interacted with.
/// </summary>
/// <param name="message">The message to wait for.</param>
/// <param name="predicate">A filter predicate.</param>
/// <param name="selectType">The <see cref="ComponentType">type</see> of the select menu.</param>
/// <param name="timeoutOverride">Override the timeout period specified in <see cref="InteractivityConfiguration"/>.</param>
/// <exception cref="ArgumentException">Thrown when the Provided message does not contain any dropdowns</exception>
public Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForSelectAsync(DiscordMessage message, Func<ComponentInteractionCreateEventArgs, bool> predicate, ComponentType selectType, TimeSpan? timeoutOverride = null)
=> this.WaitForSelectAsync(message, predicate, selectType, this.GetCancellationToken(timeoutOverride));
/// <summary>
/// Waits for any dropdown to be interacted with.
/// </summary>
/// <param name="message">The message to wait for.</param>
/// <param name="predicate">A filter predicate.</param>
/// <param name="selectType">The <see cref="ComponentType">type</see> of the select menu.</param>
/// <param name="token">A token that can be used to cancel interactivity. Pass <see cref="CancellationToken.None"/> to wait indefinitely.</param>
/// <exception cref="ArgumentException">Thrown when the Provided message does not contain any dropdowns</exception>
public async Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForSelectAsync(DiscordMessage message, Func<ComponentInteractionCreateEventArgs, bool> predicate, ComponentType selectType, 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 == selectType))
throw new ArgumentException("Message does not contain any select components.");
var result = await this
._componentEventWaiter
.WaitForMatchAsync(new ComponentMatchRequest(message, c => c.Interaction.Data.ComponentType == selectType && predicate(c), token))
.ConfigureAwait(false);
return new InteractivityResult<ComponentInteractionCreateEventArgs>(result is null, result);
}
/// <summary>
/// Waits for a dropdown to be interacted with.
/// </summary>
/// <remarks>This is here for backwards-compatibility and will internally create a cancellation token.</remarks>
/// <param name="message">The message to wait on.</param>
/// <param name="id">The Id of the dropdown to wait on.</param>
/// <param name="selectType">The <see cref="ComponentType">type</see> of the select menu.</param>
/// <param name="timeoutOverride">Override the timeout period specified in <see cref="InteractivityConfiguration"/>.</param>
/// <exception cref="ArgumentException">Thrown when the message does not have any dropdowns or any dropdown with the specified Id.</exception>
public Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForSelectAsync(DiscordMessage message, string id, ComponentType selectType, TimeSpan? timeoutOverride = null)
=> this.WaitForSelectAsync(message, id, selectType, this.GetCancellationToken(timeoutOverride));
/// <summary>
/// Waits for a dropdown to be interacted with.
/// </summary>
/// <param name="message">The message to wait on.</param>
/// <param name="id">The Id of the dropdown to wait on.</param>
/// <param name="selectType">The <see cref="ComponentType">type</see> of the select menu.</param>
/// <param name="token">A custom cancellation token that can be cancelled at any point.</param>
/// <exception cref="ArgumentException">Thrown when the message does not have any dropdowns or any dropdown with the specified Id.</exception>
public async Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForSelectAsync(DiscordMessage message, string id, ComponentType selectType, 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 == selectType))
throw new ArgumentException("Message does not contain any select components.");
if (message.Components.SelectMany(c => c.Components).OfType<DiscordBaseSelectComponent>().All(c => c.CustomId != id))
throw new ArgumentException($"Message does not contain select component with Id of '{id}'.");
var result = await this
._componentEventWaiter
.WaitForMatchAsync(new ComponentMatchRequest(message, (c) => c.Interaction.Data.ComponentType == selectType && c.Id == id, token))
.ConfigureAwait(false);
return new InteractivityResult<ComponentInteractionCreateEventArgs>(result is null, result);
}
/// <summary>
/// Waits for a dropdown to be interacted with by a specific user.
/// </summary>
/// <param name="message">The message to wait on.</param>
/// <param name="user">The user to wait on.</param>
/// <param name="id">The Id of the dropdown to wait on.</param>
/// <param name="selectType">The <see cref="ComponentType">type</see> of the select menu.</param>
/// <param name="timeoutOverride">Override the timeout period specified in <see cref="InteractivityConfiguration"/>.</param>
/// <exception cref="ArgumentException">Thrown when the message does not have any dropdowns or any dropdown with the specified Id.</exception>
public Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForSelectAsync(DiscordMessage message, DiscordUser user, string id, ComponentType selectType, TimeSpan? timeoutOverride = null)
=> this.WaitForSelectAsync(message, user, id, selectType, this.GetCancellationToken(timeoutOverride));
/// <summary>
/// Waits for a dropdown to be interacted with by a specific user.
/// </summary>
/// <param name="message">The message to wait on.</param>
/// <param name="user">The user to wait on.</param>
/// <param name="id">The Id of the dropdown to wait on.</param>
/// <param name="selectType">The <see cref="ComponentType">type</see> of the select menu.</param>
/// <param name="token">A custom cancellation token that can be cancelled at any point.</param>
/// <exception cref="ArgumentException">Thrown when the message does not have any dropdowns or any dropdown with the specified Id.</exception>
public async Task<InteractivityResult<ComponentInteractionCreateEventArgs>> WaitForSelectAsync(DiscordMessage message, DiscordUser user, string id, ComponentType selectType, 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 == selectType))
throw new ArgumentException("Message does not contain any select components.");
if (message.Components.SelectMany(c => c.Components).OfType<DiscordBaseSelectComponent>().All(c => c.CustomId != id))
throw new ArgumentException($"Message does not contain select with Id of '{id}'.");
var result = await this
._componentEventWaiter
.WaitForMatchAsync(new ComponentMatchRequest(message, (c) => c.Id == id && c.User == user, token)).ConfigureAwait(false);
return new InteractivityResult<ComponentInteractionCreateEventArgs>(result is null, result);
}
/// <summary>
/// Waits for a specific message.
/// </summary>
/// <param name="predicate">Predicate to match.</param>
/// <param name="timeoutOverride">Override timeout period.</param>
public async Task<InteractivityResult<DiscordMessage>> WaitForMessageAsync(Func<DiscordMessage, bool> 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<MessageCreateEventArgs>(x => predicate(x.Message), timeout)).ConfigureAwait(false);
return new InteractivityResult<DiscordMessage>(returns == null, returns?.Message);
}
/// <summary>
/// Wait for a specific reaction.
/// </summary>
/// <param name="predicate">Predicate to match.</param>
/// <param name="timeoutOverride">Override timeout period.</param>
public async Task<InteractivityResult<MessageReactionAddEventArgs>> WaitForReactionAsync(Func<MessageReactionAddEventArgs, bool> 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<MessageReactionAddEventArgs>(predicate, timeout)).ConfigureAwait(false);
return new InteractivityResult<MessageReactionAddEventArgs>(returns == null, returns);
}
/// <summary>
/// Wait for a specific reaction.
/// For this Event you need the <see cref="DiscordIntents.GuildMessageReactions"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
/// <param name="message">Message reaction was added to.</param>
/// <param name="user">User that made the reaction.</param>
/// <param name="timeoutOverride">Override timeout period.</param>
public async Task<InteractivityResult<MessageReactionAddEventArgs>> 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);
/// <summary>
/// Waits for a specific reaction.
/// For this Event you need the <see cref="DiscordIntents.GuildMessageReactions"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
/// <param name="predicate">Predicate to match.</param>
/// <param name="message">Message reaction was added to.</param>
/// <param name="user">User that made the reaction.</param>
/// <param name="timeoutOverride">Override timeout period.</param>
public async Task<InteractivityResult<MessageReactionAddEventArgs>> WaitForReactionAsync(Func<MessageReactionAddEventArgs, bool> 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);
/// <summary>
/// Waits for a specific reaction.
/// For this Event you need the <see cref="DiscordIntents.GuildMessageReactions"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
/// <param name="predicate">predicate to match.</param>
/// <param name="user">User that made the reaction.</param>
/// <param name="timeoutOverride">Override timeout period.</param>
public async Task<InteractivityResult<MessageReactionAddEventArgs>> WaitForReactionAsync(Func<MessageReactionAddEventArgs, bool> predicate,
DiscordUser user, TimeSpan? timeoutOverride = null)
=> await this.WaitForReactionAsync(x => predicate(x) && x.User.Id == user.Id, timeoutOverride).ConfigureAwait(false);
/// <summary>
/// Waits for a user to start typing.
/// </summary>
/// <param name="user">User that starts typing.</param>
/// <param name="channel">Channel the user is typing in.</param>
/// <param name="timeoutOverride">Override timeout period.</param>
public async Task<InteractivityResult<TypingStartEventArgs>> 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(
new MatchRequest<TypingStartEventArgs>(x => x.User.Id == user.Id && x.Channel.Id == channel.Id, timeout))
.ConfigureAwait(false);
return new InteractivityResult<TypingStartEventArgs>(returns == null, returns);
}
/// <summary>
/// Waits for a user to start typing.
/// </summary>
/// <param name="user">User that starts typing.</param>
/// <param name="timeoutOverride">Override timeout period.</param>
public async Task<InteractivityResult<TypingStartEventArgs>> 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(
new MatchRequest<TypingStartEventArgs>(x => x.User.Id == user.Id, timeout))
.ConfigureAwait(false);
return new InteractivityResult<TypingStartEventArgs>(returns == null, returns);
}
/// <summary>
/// Waits for any user to start typing.
/// </summary>
/// <param name="channel">Channel to type in.</param>
/// <param name="timeoutOverride">Override timeout period.</param>
public async Task<InteractivityResult<TypingStartEventArgs>> 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(
new MatchRequest<TypingStartEventArgs>(x => x.Channel.Id == channel.Id, timeout))
.ConfigureAwait(false);
return new InteractivityResult<TypingStartEventArgs>(returns == null, returns);
}
/// <summary>
/// Collects reactions on a specific message.
/// </summary>
/// <param name="m">Message to collect reactions on.</param>
/// <param name="timeoutOverride">Override timeout period.</param>
public async Task<ReadOnlyCollection<Reaction>> 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);
return collection;
}
/// <summary>
/// Waits for specific event args to be received. Make sure the appropriate <see cref="DiscordIntents"/> are registered, if needed.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="predicate">The predicate.</param>
/// <param name="timeoutOverride">Override timeout period.</param>
public async Task<InteractivityResult<T>> WaitForEventArgsAsync<T>(Func<T, bool> predicate, TimeSpan? timeoutOverride = null) where T : AsyncEventArgs
{
var timeout = timeoutOverride ?? this.Config.Timeout;
using var waiter = new EventWaiter<T>(this.Client);
var res = await waiter.WaitForMatchAsync(new MatchRequest<T>(predicate, timeout)).ConfigureAwait(false);
return new InteractivityResult<T>(res == null, res);
}
/// <summary>
/// Collects the event arguments.
/// </summary>
/// <param name="predicate">The predicate.</param>
/// <param name="timeoutOverride">Override timeout period.</param>
public async Task<ReadOnlyCollection<T>> CollectEventArgsAsync<T>(Func<T, bool> predicate, TimeSpan? timeoutOverride = null) where T : AsyncEventArgs
{
var timeout = timeoutOverride ?? this.Config.Timeout;
using var waiter = new EventWaiter<T>(this.Client);
var res = await waiter.CollectMatchesAsync(new CollectRequest<T>(predicate, timeout)).ConfigureAwait(false);
return res;
}
/// <summary>
/// Sends a paginated message with buttons.
/// </summary>
/// <param name="channel">The channel to send it on.</param>
/// <param name="user">User to give control.</param>
/// <param name="pages">The pages.</param>
/// <param name="buttons">Pagination buttons (pass null to use buttons defined in <see cref="InteractivityConfiguration"/>).</param>
/// <param name="behaviour">Pagination behaviour.</param>
/// <param name="deletion">Deletion behaviour.</param>
/// <param name="token">A custom cancellation token that can be cancelled at any point.</param>
public async Task SendPaginatedMessageAsync(
DiscordChannel channel, DiscordUser user, IEnumerable<Page> 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 PaginationButtons(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, token == default ? this.GetCancellationToken() : token);
await this._compPaginator.DoPaginationAsync(req).ConfigureAwait(false);
}
/// <summary>
/// Sends a paginated message with buttons.
/// </summary>
/// <param name="channel">The channel to send it on.</param>
/// <param name="user">User to give control.</param>
/// <param name="pages">The pages.</param>
/// <param name="buttons">Pagination buttons (pass null to use buttons defined in <see cref="InteractivityConfiguration"/>).</param>
/// <param name="behaviour">Pagination behaviour.</param>
/// <param name="deletion">Deletion behaviour.</param>
/// <param name="timeoutOverride">Override timeout period.</param>
public Task SendPaginatedMessageAsync(
DiscordChannel channel, DiscordUser user, IEnumerable<Page> pages, PaginationButtons buttons, TimeSpan? timeoutOverride,
PaginationBehaviour? behaviour = default, ButtonPaginationBehavior? deletion = default)
=> this.SendPaginatedMessageAsync(channel, user, pages, buttons, behaviour, deletion, this.GetCancellationToken(timeoutOverride));
/// <summary>
/// Sends the paginated message.
/// </summary>
/// <param name="channel">The channel.</param>
/// <param name="user">The user.</param>
/// <param name="pages">The pages.</param>
/// <param name="behaviour">The behaviour.</param>
/// <param name="deletion">The deletion.</param>
/// <param name="token">The token.</param>
/// <returns>A Task.</returns>
public Task SendPaginatedMessageAsync(DiscordChannel channel, DiscordUser user, IEnumerable<Page> pages, PaginationBehaviour? behaviour = default, ButtonPaginationBehavior? deletion = default, CancellationToken token = default)
=> this.SendPaginatedMessageAsync(channel, user, pages, default, behaviour, deletion, token);
/// <summary>
/// Sends the paginated message.
/// </summary>
/// <param name="channel">The channel.</param>
/// <param name="user">The user.</param>
/// <param name="pages">The pages.</param>
/// <param name="timeoutOverride">Override timeout period.</param>
/// <param name="behaviour">The behaviour.</param>
/// <param name="deletion">The deletion.</param>
/// <returns>A Task.</returns>
public Task SendPaginatedMessageAsync(DiscordChannel channel, DiscordUser user, IEnumerable<Page> pages, TimeSpan? timeoutOverride, PaginationBehaviour? behaviour = default, ButtonPaginationBehavior? deletion = default)
=> this.SendPaginatedMessageAsync(channel, user, pages, timeoutOverride, behaviour, deletion);
/// <summary>
/// Sends a paginated message.
/// For this Event you need the <see cref="DiscordIntents.GuildMessageReactions"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
/// <param name="channel">Channel to send paginated message in.</param>
/// <param name="user">User to give control.</param>
/// <param name="pages">Pages.</param>
/// <param name="emojis">Pagination emojis.</param>
/// <param name="behaviour">Pagination behaviour (when hitting max and min indices).</param>
/// <param name="deletion">Deletion behaviour.</param>
/// <param name="timeoutOverride">Override timeout period.</param>
public async Task SendPaginatedMessageAsync(DiscordChannel channel, DiscordUser user, IEnumerable<Page> 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);
await this._paginator.DoPaginationAsync(pRequest).ConfigureAwait(false);
}
/// <summary>
/// Sends a paginated message in response to an interaction.
/// <para>
/// <b>Pass the interaction directly. Interactivity will ACK it.</b>
/// </para>
/// </summary>
/// <param name="interaction">The interaction to create a response to.</param>
/// <param name="deferred">Whether the interaction was deferred.</param>
/// <param name="ephemeral">Whether the response should be ephemeral.</param>
/// <param name="user">The user to listen for button presses from.</param>
/// <param name="pages">The pages to paginate.</param>
/// <param name="buttons">Optional: custom buttons.</param>
/// <param name="behaviour">Pagination behaviour.</param>
/// <param name="deletion">Deletion behaviour.</param>
/// <param name="token">A custom cancellation token that can be cancelled at any point.</param>
public async Task SendPaginatedResponseAsync(DiscordInteraction interaction, bool deferred, bool ephemeral, DiscordUser user, IEnumerable<Page> 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 PaginationButtons(bts);
if (bhv is PaginationBehaviour.Ignore)
{
bts.SkipLeft.Disable();
bts.Left.Disable();
}
DiscordMessage message;
if (deferred)
{
var builder = new DiscordWebhookBuilder()
.WithContent(pages.First().Content)
.AddEmbed(pages.First().Embed)
.AddComponents(bts.ButtonArray);
message = await interaction.EditOriginalResponseAsync(builder).ConfigureAwait(false);
}
else
{
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);
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);
}
/// <summary>
/// Waits for a custom pagination request to finish.
/// This does NOT handle removing emojis after finishing for you.
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public async Task WaitForCustomPaginationAsync(IPaginationRequest request) => await this._paginator.DoPaginationAsync(request).ConfigureAwait(false);
/// <summary>
/// Waits for custom button-based pagination request to finish.
/// <br/>
/// This does <i><b>not</b></i> invoke <see cref="DisCatSharp.Interactivity.EventHandling.IPaginationRequest.DoCleanupAsync"/>.
/// </summary>
/// <param name="request">The request to wait for.</param>
public async Task WaitForCustomComponentPaginationAsync(IPaginationRequest request) => await this._compPaginator.DoPaginationAsync(request).ConfigureAwait(false);
/// <summary>
/// Generates pages from a string, and puts them in message content.
/// </summary>
/// <param name="input">Input string.</param>
/// <param name="splitType">How to split input string.</param>
/// <returns></returns>
public IEnumerable<Page> 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<Page>();
List<string> 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<string>();
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;
}
/// <summary>
/// Generates pages from a string, and puts them in message embeds.
/// </summary>
/// <param name="input">Input string.</param>
/// <param name="splitType">How to split input string.</param>
/// <param name="embedBase">Base embed for output embeds.</param>
/// <returns></returns>
public IEnumerable<Page> 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<Page>();
List<string> 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<string>();
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;
}
/// <summary>
/// Splits the string.
/// </summary>
/// <param name="str">The string.</param>
/// <param name="chunkSize">The chunk size.</param>
private List<string> SplitString(string str, int chunkSize)
{
var res = new List<string>();
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;
}
/// <summary>
/// Gets the cancellation token.
/// </summary>
/// <param name="timeout">The timeout.</param>
private CancellationToken GetCancellationToken(TimeSpan? timeout = null) => new CancellationTokenSource(timeout ?? this.Config.Timeout).Token;
/// <summary>
/// Handles an invalid interaction.
/// </summary>
/// <param name="interaction">The interaction.</param>
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 DiscordInteractionResponseBuilder { Content = this.Config.ResponseMessage, IsEphemeral = true}),
InteractionResponseBehavior.Ignore => Task.CompletedTask,
_ => throw new ArgumentException("Unknown enum value.")
};
await at;
}
}
diff --git a/DisCatSharp.Interactivity/InteractivityResult.cs b/DisCatSharp.Interactivity/InteractivityResult.cs
index 98273ccb9..a38b9dda8 100644
--- a/DisCatSharp.Interactivity/InteractivityResult.cs
+++ b/DisCatSharp.Interactivity/InteractivityResult.cs
@@ -1,50 +1,50 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Interactivity;
/// <summary>
/// Interactivity result
/// </summary>
/// <typeparam name="T">Type of result</typeparam>
public readonly struct InteractivityResult<T>
{
/// <summary>
/// Whether interactivity was timed out
/// </summary>
public bool TimedOut { get; }
/// <summary>
/// Result
/// </summary>
public T Result { get; }
/// <summary>
/// Initializes a new instance of the <see cref="InteractivityResult{T}"/> class.
/// </summary>
/// <param name="timedOut">If true, timed out.</param>
/// <param name="result">The result.</param>
internal InteractivityResult(bool timedOut, T result)
{
this.TimedOut = timedOut;
this.Result = result;
}
}
diff --git a/DisCatSharp.Interactivity/Properties/AssemblyProperties.cs b/DisCatSharp.Interactivity/Properties/AssemblyProperties.cs
index a94e719fd..f7ff00198 100644
--- a/DisCatSharp.Interactivity/Properties/AssemblyProperties.cs
+++ b/DisCatSharp.Interactivity/Properties/AssemblyProperties.cs
@@ -1,46 +1,46 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("DisCatSharp.ApplicationCommands")]
[assembly: InternalsVisibleTo("DisCatSharp.CommandsNext")]
[assembly: InternalsVisibleTo("DisCatSharp.Experimental")]
[assembly: InternalsVisibleTo("DisCatSharp.Common")]
[assembly: InternalsVisibleTo("DisCatSharp.Configuration")]
[assembly: InternalsVisibleTo("DisCatSharp.Configuration.Tests")]
[assembly: InternalsVisibleTo("DisCatSharp.Hosting")]
[assembly: InternalsVisibleTo("DisCatSharp.Hosting.DependencyInjection")]
[assembly: InternalsVisibleTo("DisCatSharp.Hosting.Tests")]
[assembly: InternalsVisibleTo("DisCatSharp")]
[assembly: InternalsVisibleTo("DisCatSharp.Lavalink")]
[assembly: InternalsVisibleTo("DisCatSharp.Phabricator")]
[assembly: InternalsVisibleTo("DisCatSharp.Support")]
[assembly: InternalsVisibleTo("DisCatSharp.Test")]
[assembly: InternalsVisibleTo("DisCatSharp.VoiceNext")]
[assembly: InternalsVisibleTo("DisCatSharp.VoiceNext.Natives")]
[assembly: InternalsVisibleTo("Nyaw")]
[assembly: InternalsVisibleTo("DisCatSharp.DevTools")]
[assembly: InternalsVisibleTo("DisCatSharp.DocsGenerator")]
[assembly: InternalsVisibleTo("DisCatSharp.StaffApps")]
[assembly: InternalsVisibleTo("Microsoft.DocAsCode")]
[assembly: InternalsVisibleTo("Microsoft.DocAsCode.Metadata.ManagedReference")]
diff --git a/DisCatSharp.Lavalink/DiscordClientExtensions.cs b/DisCatSharp.Lavalink/DiscordClientExtensions.cs
index 8ea14895e..b2fd22791 100644
--- a/DisCatSharp.Lavalink/DiscordClientExtensions.cs
+++ b/DisCatSharp.Lavalink/DiscordClientExtensions.cs
@@ -1,131 +1,131 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.Entities;
using DisCatSharp.Enums;
using Microsoft.Extensions.Logging;
namespace DisCatSharp.Lavalink;
/// <summary>
/// The discord client extensions.
/// </summary>
public static class DiscordClientExtensions
{
/// <summary>
/// Creates a new Lavalink client with specified settings.
/// </summary>
/// <param name="client">Discord client to create Lavalink instance for.</param>
/// <returns>Lavalink client instance.</returns>
public static LavalinkExtension UseLavalink(this DiscordClient client)
{
if (client.GetExtension<LavalinkExtension>() != null)
throw new InvalidOperationException("Lavalink is already enabled for that client.");
if (!client.Configuration.Intents.HasIntent(DiscordIntents.GuildVoiceStates))
client.Logger.LogCritical(LavalinkEvents.Intents, "The Lavalink extension is registered but the guild voice states intent is not enabled. It is highly recommended to enable it.");
var lava = new LavalinkExtension();
client.AddExtension(lava);
return lava;
}
/// <summary>
/// Creates new Lavalink clients on all shards in a given sharded client.
/// </summary>
/// <param name="client">Discord sharded client to create Lavalink instances for.</param>
/// <returns>A dictionary of created Lavalink clients.</returns>
public static async Task<IReadOnlyDictionary<int, LavalinkExtension>> UseLavalinkAsync(this DiscordShardedClient client)
{
var modules = new Dictionary<int, LavalinkExtension>();
await client.InitializeShardsAsync().ConfigureAwait(false);
foreach (var shard in client.ShardClients.Select(xkvp => xkvp.Value))
{
var lava = shard.GetExtension<LavalinkExtension>();
lava ??= shard.UseLavalink();
modules[shard.ShardId] = lava;
}
return new ReadOnlyDictionary<int, LavalinkExtension>(modules);
}
/// <summary>
/// Gets the active instance of the Lavalink client for the DiscordClient.
/// </summary>
/// <param name="client">Discord client to get Lavalink instance for.</param>
/// <returns>Lavalink client instance.</returns>
public static LavalinkExtension GetLavalink(this DiscordClient client)
=> client.GetExtension<LavalinkExtension>();
/// <summary>
/// Retrieves a <see cref="LavalinkExtension"/> instance for each shard.
/// </summary>
/// <param name="client">The shard client to retrieve <see cref="LavalinkExtension"/> instances from.</param>
/// <returns>A dictionary containing <see cref="LavalinkExtension"/> instances for each shard.</returns>
public static async Task<IReadOnlyDictionary<int, LavalinkExtension>> GetLavalinkAsync(this DiscordShardedClient client)
{
await client.InitializeShardsAsync().ConfigureAwait(false);
var extensions = new Dictionary<int, LavalinkExtension>();
foreach (var shard in client.ShardClients.Values)
{
extensions.Add(shard.ShardId, shard.GetExtension<LavalinkExtension>());
}
return new ReadOnlyDictionary<int, LavalinkExtension>(extensions);
}
/// <summary>
/// Connects to this voice channel using Lavalink.
/// </summary>
/// <param name="channel">Channel to connect to.</param>
/// <param name="node">Lavalink node to connect through.</param>
/// <returns>If successful, the Lavalink client.</returns>
public static Task ConnectAsync(this DiscordChannel channel, LavalinkNodeConnection node)
{
if (channel == null)
throw new NullReferenceException();
if (channel.Guild == null)
throw new InvalidOperationException("Lavalink can only be used with guild channels.");
if (channel.Type != ChannelType.Voice && channel.Type != ChannelType.Stage)
throw new InvalidOperationException("You can only connect to voice and stage channels.");
if (channel.Discord is not DiscordClient discord || discord == null)
throw new NullReferenceException();
var lava = discord.GetLavalink();
return lava == null
? throw new InvalidOperationException("Lavalink is not initialized for this Discord client.")
: node.ConnectAsync(channel);
}
}
diff --git a/DisCatSharp.Lavalink/Entities/LavalinkCommands.cs b/DisCatSharp.Lavalink/Entities/LavalinkCommands.cs
index f11e34618..abe8f3ea7 100644
--- a/DisCatSharp.Lavalink/Entities/LavalinkCommands.cs
+++ b/DisCatSharp.Lavalink/Entities/LavalinkCommands.cs
@@ -1,241 +1,241 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 Newtonsoft.Json;
namespace DisCatSharp.Lavalink.Entities;
/// <summary>
/// The lavalink configure resume.
/// </summary>
internal sealed class LavalinkConfigureResume : LavalinkPayload
{
/// <summary>
/// Gets the key.
/// </summary>
[JsonProperty("key")]
public string Key { get; }
/// <summary>
/// Gets the timeout.
/// </summary>
[JsonProperty("timeout")]
public int Timeout { get; }
/// <summary>
/// Initializes a new instance of the <see cref="LavalinkConfigureResume"/> class.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="timeout">The timeout.</param>
public LavalinkConfigureResume(string key, int timeout)
: base("configureResuming")
{
this.Key = key;
this.Timeout = timeout;
}
}
/// <summary>
/// The lavalink destroy.
/// </summary>
internal sealed class LavalinkDestroy : LavalinkPayload
{
/// <summary>
/// Initializes a new instance of the <see cref="LavalinkDestroy"/> class.
/// </summary>
/// <param name="lvl">The lvl.</param>
public LavalinkDestroy(LavalinkGuildConnection lvl)
: base("destroy", lvl.GuildIdString)
{ }
}
/// <summary>
/// The lavalink play.
/// </summary>
internal sealed class LavalinkPlay : LavalinkPayload
{
/// <summary>
/// Gets the track.
/// </summary>
[JsonProperty("track")]
public string Track { get; }
/// <summary>
/// Initializes a new instance of the <see cref="LavalinkPlay"/> class.
/// </summary>
/// <param name="lvl">The lvl.</param>
/// <param name="track">The track.</param>
public LavalinkPlay(LavalinkGuildConnection lvl, LavalinkTrack track)
: base("play", lvl.GuildIdString)
{
this.Track = track.TrackString;
}
}
/// <summary>
/// The lavalink play partial.
/// </summary>
internal sealed class LavalinkPlayPartial : LavalinkPayload
{
/// <summary>
/// Gets the track.
/// </summary>
[JsonProperty("track")]
public string Track { get; }
/// <summary>
/// Gets the start time.
/// </summary>
[JsonProperty("startTime")]
public long StartTime { get; }
/// <summary>
/// Gets the stop time.
/// </summary>
[JsonProperty("endTime")]
public long StopTime { get; }
/// <summary>
/// Initializes a new instance of the <see cref="LavalinkPlayPartial"/> class.
/// </summary>
/// <param name="lvl">The lvl.</param>
/// <param name="track">The track.</param>
/// <param name="start">The start.</param>
/// <param name="stop">The stop.</param>
public LavalinkPlayPartial(LavalinkGuildConnection lvl, LavalinkTrack track, TimeSpan start, TimeSpan stop)
: base("play", lvl.GuildIdString)
{
this.Track = track.TrackString;
this.StartTime = (long)start.TotalMilliseconds;
this.StopTime = (long)stop.TotalMilliseconds;
}
}
/// <summary>
/// The lavalink pause.
/// </summary>
internal sealed class LavalinkPause : LavalinkPayload
{
/// <summary>
/// Gets a value indicating whether pause.
/// </summary>
[JsonProperty("pause")]
public bool Pause { get; }
/// <summary>
/// Initializes a new instance of the <see cref="LavalinkPause"/> class.
/// </summary>
/// <param name="lvl">The lvl.</param>
/// <param name="pause">If true, pause.</param>
public LavalinkPause(LavalinkGuildConnection lvl, bool pause)
: base("pause", lvl.GuildIdString)
{
this.Pause = pause;
}
}
/// <summary>
/// The lavalink stop.
/// </summary>
internal sealed class LavalinkStop : LavalinkPayload
{
/// <summary>
/// Initializes a new instance of the <see cref="LavalinkStop"/> class.
/// </summary>
/// <param name="lvl">The lvl.</param>
public LavalinkStop(LavalinkGuildConnection lvl)
: base("stop", lvl.GuildIdString)
{ }
}
/// <summary>
/// The lavalink seek.
/// </summary>
internal sealed class LavalinkSeek : LavalinkPayload
{
/// <summary>
/// Gets the position.
/// </summary>
[JsonProperty("position")]
public long Position { get; }
/// <summary>
/// Initializes a new instance of the <see cref="LavalinkSeek"/> class.
/// </summary>
/// <param name="lvl">The lvl.</param>
/// <param name="position">The position.</param>
public LavalinkSeek(LavalinkGuildConnection lvl, TimeSpan position)
: base("seek", lvl.GuildIdString)
{
this.Position = (long)position.TotalMilliseconds;
}
}
/// <summary>
/// The lavalink volume.
/// </summary>
internal sealed class LavalinkVolume : LavalinkPayload
{
/// <summary>
/// Gets the volume.
/// </summary>
[JsonProperty("volume")]
public int Volume { get; }
/// <summary>
/// Initializes a new instance of the <see cref="LavalinkVolume"/> class.
/// </summary>
/// <param name="lvl">The lvl.</param>
/// <param name="volume">The volume.</param>
public LavalinkVolume(LavalinkGuildConnection lvl, int volume)
: base("volume", lvl.GuildIdString)
{
this.Volume = volume;
}
}
/// <summary>
/// The lavalink equalizer.
/// </summary>
internal sealed class LavalinkEqualizer : LavalinkPayload
{
/// <summary>
/// Gets the bands.
/// </summary>
[JsonProperty("bands")]
public List<LavalinkBandAdjustment> Bands { get; }
/// <summary>
/// Initializes a new instance of the <see cref="LavalinkEqualizer"/> class.
/// </summary>
/// <param name="lvl">The lvl.</param>
/// <param name="bands">The bands.</param>
public LavalinkEqualizer(LavalinkGuildConnection lvl, IEnumerable<LavalinkBandAdjustment> bands)
: base("equalizer", lvl.GuildIdString)
{
this.Bands = bands.ToList();
}
}
diff --git a/DisCatSharp.Lavalink/Entities/LavalinkDispatch.cs b/DisCatSharp.Lavalink/Entities/LavalinkDispatch.cs
index e324853cf..d1ec51ed2 100644
--- a/DisCatSharp.Lavalink/Entities/LavalinkDispatch.cs
+++ b/DisCatSharp.Lavalink/Entities/LavalinkDispatch.cs
@@ -1,55 +1,55 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Lavalink.Entities;
/// <summary>
/// The voice dispatch.
/// </summary>
internal sealed class VoiceDispatch
{
/// <summary>
/// Gets or sets the op code.
/// </summary>
[JsonProperty("op")]
public int OpCode { get; set; }
/// <summary>
/// Gets or sets the payload.
/// </summary>
[JsonProperty("d")]
public object Payload { get; set; }
/// <summary>
/// Gets or sets the sequence.
/// </summary>
[JsonProperty("s", NullValueHandling = NullValueHandling.Ignore)]
public int? Sequence { get; set; }
/// <summary>
/// Gets or sets the event name.
/// </summary>
[JsonProperty("t", NullValueHandling = NullValueHandling.Ignore)]
public string EventName { get; set; }
}
diff --git a/DisCatSharp.Lavalink/Entities/LavalinkEqualizerTypes.cs b/DisCatSharp.Lavalink/Entities/LavalinkEqualizerTypes.cs
index 1d4af2375..8f73065e9 100644
--- a/DisCatSharp.Lavalink/Entities/LavalinkEqualizerTypes.cs
+++ b/DisCatSharp.Lavalink/Entities/LavalinkEqualizerTypes.cs
@@ -1,84 +1,84 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Lavalink;
/// <summary>
/// Represents Lavalink equalizer band adjustment. This is used to alter the sound output by using Lavalink's equalizer.
/// </summary>
public readonly struct LavalinkBandAdjustment
{
/// <summary>
/// Gets the ID of the band to adjust.
/// </summary>
[JsonProperty("band")]
public int BandId { get; }
/// <summary>
/// Gets the gain of the specified band.
/// </summary>
[JsonProperty("gain")]
public float Gain { get; }
/// <summary>
/// Creates a new band adjustment with specified parameters.
/// </summary>
/// <param name="bandId">Which band to adjust. Must be in 0-14 range.</param>
/// <param name="gain">By how much to adjust the band. Must be greater than or equal to -0.25 (muted), and less than or equal to +1.0. +0.25 means the band is doubled.</param>
public LavalinkBandAdjustment(int bandId, float gain)
{
if (bandId < 0 || bandId > 14)
throw new ArgumentOutOfRangeException(nameof(bandId), "Band ID cannot be lower than 0 or greater than 14.");
if (gain < -0.25 || gain > 1.0)
throw new ArgumentOutOfRangeException(nameof(gain), "Gain cannot be lower than -0.25 or greater than 1.0.");
this.BandId = bandId;
this.Gain = gain;
}
}
/// <summary>
/// The lavalink band adjustment comparer.
/// </summary>
internal class LavalinkBandAdjustmentComparer : IEqualityComparer<LavalinkBandAdjustment>
{
/// <summary>
/// Whether two band adjustments are equal.
/// </summary>
/// <param name="x">The first band adjustments.</param>
/// <param name="y">The second band adjustments.</param>
public bool Equals(LavalinkBandAdjustment x, LavalinkBandAdjustment y)
=> x.BandId == y.BandId;
/// <summary>
/// Gets the hash code.
/// </summary>
/// <param name="obj">The band adjustments.</param>
public int GetHashCode(LavalinkBandAdjustment obj)
=> obj.BandId;
}
diff --git a/DisCatSharp.Lavalink/Entities/LavalinkPayload.cs b/DisCatSharp.Lavalink/Entities/LavalinkPayload.cs
index 2ba94b6b8..54bd4ae97 100644
--- a/DisCatSharp.Lavalink/Entities/LavalinkPayload.cs
+++ b/DisCatSharp.Lavalink/Entities/LavalinkPayload.cs
@@ -1,63 +1,63 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Lavalink.Entities;
/// <summary>
/// The lavalink payload.
/// </summary>
internal abstract class LavalinkPayload
{
/// <summary>
/// Gets the operation.
/// </summary>
[JsonProperty("op")]
public string Operation { get; }
/// <summary>
/// Gets the guild id.
/// </summary>
[JsonProperty("guildId", NullValueHandling = NullValueHandling.Ignore)]
public string GuildId { get; }
/// <summary>
/// Initializes a new instance of the <see cref="LavalinkPayload"/> class.
/// </summary>
/// <param name="opcode">The opcode.</param>
internal LavalinkPayload(string opcode)
{
this.Operation = opcode;
}
/// <summary>
/// Initializes a new instance of the <see cref="LavalinkPayload"/> class.
/// </summary>
/// <param name="opcode">The opcode.</param>
/// <param name="guildId">The guild id.</param>
internal LavalinkPayload(string opcode, string guildId)
{
this.Operation = opcode;
this.GuildId = guildId;
}
}
diff --git a/DisCatSharp.Lavalink/Entities/LavalinkRouteStatus.cs b/DisCatSharp.Lavalink/Entities/LavalinkRouteStatus.cs
index 1ae4a4112..7fbd7ed0e 100644
--- a/DisCatSharp.Lavalink/Entities/LavalinkRouteStatus.cs
+++ b/DisCatSharp.Lavalink/Entities/LavalinkRouteStatus.cs
@@ -1,155 +1,155 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Lavalink.Entities;
/// <summary>
/// The lavalink route status.
/// </summary>
public class LavalinkRouteStatus
{
/// <summary>
/// Gets the route planner type.
/// </summary>
[JsonIgnore]
public LavalinkRoutePlannerType? Class
=> this.GetLavalinkRoutePlannerType(this.PlannerTypeClass);
/// <summary>
/// Gets the details of the route planner.
/// </summary>
[JsonProperty("details", NullValueHandling = NullValueHandling.Ignore)]
public LavalinkRouteStatusDetails Details { get; internal set; }
/// <summary>
/// Gets or sets the class.
/// </summary>
[JsonProperty("class", NullValueHandling = NullValueHandling.Ignore)]
internal string PlannerTypeClass { get; set; }
/// <summary>
/// Gets the lavalink route planner type.
/// </summary>
/// <param name="type">The type.</param>
private LavalinkRoutePlannerType? GetLavalinkRoutePlannerType(string type) =>
type switch
{
"RotatingIpRoutePlanner" => LavalinkRoutePlannerType.RotatingIpRoutePlanner,
"BalancingIpRoutePlanner" => LavalinkRoutePlannerType.BalancingIpRoutePlanner,
"NanoIpRoutePlanner" => LavalinkRoutePlannerType.NanoIpRoutePlanner,
"RotatingNanoIpRoutePlanner" => LavalinkRoutePlannerType.RotatingNanoIpRoutePlanner,
_ => null,
};
}
/// <summary>
/// The lavalink route status details.
/// </summary>
public class LavalinkRouteStatusDetails
{
/// <summary>
/// Gets the details for the current IP block.
/// </summary>
[JsonProperty("ipBlock", NullValueHandling = NullValueHandling.Ignore)]
public LavalinkIpBlock IpBlock { get; internal set; }
/// <summary>
/// Gets the collection of failed addresses.
/// </summary>
[JsonProperty("failingAddresses", NullValueHandling = NullValueHandling.Ignore)]
public List<LavalinkFailedAddress> FailedAddresses { get; internal set; }
/// <summary>
/// Gets the number of rotations since the restart of Lavalink.
/// <para>Only present in the <see cref="LavalinkRoutePlannerType.RotatingIpRoutePlanner"/>.</para>
/// </summary>
[JsonProperty("rotateIndex", NullValueHandling = NullValueHandling.Ignore)]
public string RotateIndex { get; internal set; }
/// <summary>
/// Gets the current offset of the IP block.
/// <para>Only present in the <see cref="LavalinkRoutePlannerType.RotatingIpRoutePlanner"/>.</para>
/// </summary>
[JsonProperty("ipIndex", NullValueHandling = NullValueHandling.Ignore)]
public string IpIndex { get; internal set; }
/// <summary>
/// Gets the current IP Address used by the planner.
/// <para>Only present in the <see cref="LavalinkRoutePlannerType.RotatingIpRoutePlanner"/>.</para>
/// </summary>
[JsonProperty("currentAddress", NullValueHandling = NullValueHandling.Ignore)]
public string CurrentAddress { get; internal set; }
/// <summary>
/// Gets the current offset of the IP block.
/// <para>Only present in the <see cref="LavalinkRoutePlannerType.NanoIpRoutePlanner"/> and the <see cref="LavalinkRoutePlannerType.RotatingNanoIpRoutePlanner"/>.</para>
/// </summary>
[JsonProperty("currentAddressIndex", NullValueHandling = NullValueHandling.Ignore)]
public long CurrentAddressIndex { get; internal set; }
/// <summary>
/// Gets the information in which /64 block ips are chosen. This number increases on each ban.
/// <para>Only present in the <see cref="LavalinkRoutePlannerType.RotatingNanoIpRoutePlanner"/>.</para>
/// </summary>
[JsonProperty("blockIndex", NullValueHandling = NullValueHandling.Ignore)]
public string BlockIndex { get; internal set; }
}
public struct LavalinkIpBlock
{
/// <summary>
/// Gets the type of the IP block.
/// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
public string Type { get; internal set; }
/// <summary>
/// Gets the size of the IP block.
/// </summary>
[JsonProperty("size", NullValueHandling = NullValueHandling.Ignore)]
public string Size { get; internal set; }
}
public struct LavalinkFailedAddress
{
/// <summary>
/// Gets the failed address IP.
/// </summary>
[JsonProperty("address", NullValueHandling = NullValueHandling.Ignore)]
public string Address { get; internal set; }
/// <summary>
/// Gets the failing timestamp in milliseconds.
/// </summary>
[JsonProperty("failingTimestamp", NullValueHandling = NullValueHandling.Ignore)]
public ulong FailingTimestamp { get; internal set; }
/// <summary>
/// Gets the DateTime format of the failing address.
/// </summary>
[JsonProperty("failingTime", NullValueHandling = NullValueHandling.Ignore)]
public string FailingTime { get; internal set; }
}
diff --git a/DisCatSharp.Lavalink/Entities/LavalinkUpdates.cs b/DisCatSharp.Lavalink/Entities/LavalinkUpdates.cs
index b68b16d26..af518fd45 100644
--- a/DisCatSharp.Lavalink/Entities/LavalinkUpdates.cs
+++ b/DisCatSharp.Lavalink/Entities/LavalinkUpdates.cs
@@ -1,311 +1,311 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// The lavalink state.
/// </summary>
internal sealed class LavalinkState
{
/// <summary>
/// Gets the time.
/// </summary>
[JsonIgnore]
public DateTimeOffset Time => Utilities.GetDateTimeOffsetFromMilliseconds(this._time);
[JsonProperty("time")]
private readonly long _time;
/// <summary>
/// Gets the position.
/// </summary>
[JsonIgnore]
public TimeSpan Position => TimeSpan.FromMilliseconds(this._position);
[JsonProperty("position")]
private readonly long _position;
[JsonProperty("connected")]
public bool IsConnected;
}
/// <summary>
/// Represents current state of given player.
/// </summary>
public sealed class LavalinkPlayerState
{
/// <summary>
/// Gets the timestamp at which this state was last updated.
/// </summary>
public DateTimeOffset LastUpdate { get; internal set; }
/// <summary>
/// Gets the current playback position.
/// </summary>
public TimeSpan PlaybackPosition { get; internal set; }
/// <summary>
/// Gets the currently-played track.
/// </summary>
public LavalinkTrack CurrentTrack { get; internal set; }
/// <summary>
/// Gets whether the player is currently connected to the voice gateway.
/// </summary>
public bool IsConnected { get; internal set; }
}
/// <summary>
/// The lavalink stats.
/// </summary>
internal sealed class LavalinkStats
{
/// <summary>
/// Gets or sets the active players.
/// </summary>
[JsonProperty("playingPlayers")]
public int ActivePlayers { get; set; }
/// <summary>
/// Gets or sets the total players.
/// </summary>
[JsonProperty("players")]
public int TotalPlayers { get; set; }
/// <summary>
/// Gets the uptime.
/// </summary>
[JsonIgnore]
public TimeSpan Uptime => TimeSpan.FromMilliseconds(this._uptime);
[JsonProperty("uptime")]
private readonly long _uptime;
/// <summary>
/// Gets or sets the cpu.
/// </summary>
[JsonProperty("cpu")]
public CpuStats Cpu { get; set; }
/// <summary>
/// Gets or sets the memory.
/// </summary>
[JsonProperty("memory")]
public MemoryStats Memory { get; set; }
/// <summary>
/// Gets or sets the frames.
/// </summary>
[JsonProperty("frameStats")]
public FrameStats Frames { get; set; }
/// <summary>
/// The cpu stats.
/// </summary>
internal sealed class CpuStats
{
/// <summary>
/// Gets or sets the cores.
/// </summary>
[JsonProperty("cores")]
public int Cores { get; set; }
/// <summary>
/// Gets or sets the system load.
/// </summary>
[JsonProperty("systemLoad")]
public double SystemLoad { get; set; }
/// <summary>
/// Gets or sets the lavalink load.
/// </summary>
[JsonProperty("lavalinkLoad")]
public double LavalinkLoad { get; set; }
}
/// <summary>
/// The memory stats.
/// </summary>
internal sealed class MemoryStats
{
/// <summary>
/// Gets or sets the reservable.
/// </summary>
[JsonProperty("reservable")]
public long Reservable { get; set; }
/// <summary>
/// Gets or sets the used.
/// </summary>
[JsonProperty("used")]
public long Used { get; set; }
/// <summary>
/// Gets or sets the free.
/// </summary>
[JsonProperty("free")]
public long Free { get; set; }
/// <summary>
/// Gets or sets the allocated.
/// </summary>
[JsonProperty("allocated")]
public long Allocated { get; set; }
}
/// <summary>
/// The frame stats.
/// </summary>
internal sealed class FrameStats
{
/// <summary>
/// Gets or sets the sent.
/// </summary>
[JsonProperty("sent")]
public int Sent { get; set; }
/// <summary>
/// Gets or sets the nulled.
/// </summary>
[JsonProperty("nulled")]
public int Nulled { get; set; }
/// <summary>
/// Gets or sets the deficit.
/// </summary>
[JsonProperty("deficit")]
public int Deficit { get; set; }
}
}
/// <summary>
/// Represents statistics of Lavalink resource usage.
/// </summary>
public sealed class LavalinkStatistics
{
/// <summary>
/// Gets the number of currently-playing players.
/// </summary>
public int ActivePlayers { get; private set; }
/// <summary>
/// Gets the total number of players.
/// </summary>
public int TotalPlayers { get; private set; }
/// <summary>
/// Gets the node uptime.
/// </summary>
public TimeSpan Uptime { get; private set; }
/// <summary>
/// Gets the number of CPU cores available.
/// </summary>
public int CpuCoreCount { get; private set; }
/// <summary>
/// Gets the total % of CPU resources in use on the system.
/// </summary>
public double CpuSystemLoad { get; private set; }
/// <summary>
/// Gets the total % of CPU resources used by lavalink.
/// </summary>
public double CpuLavalinkLoad { get; private set; }
/// <summary>
/// Gets the amount of reservable RAM, in bytes.
/// </summary>
public long RamReservable { get; private set; }
/// <summary>
/// Gets the amount of used RAM, in bytes.
/// </summary>
public long RamUsed { get; private set; }
/// <summary>
/// Gets the amount of free RAM, in bytes.
/// </summary>
public long RamFree { get; private set; }
/// <summary>
/// Gets the amount of allocated RAM, in bytes.
/// </summary>
public long RamAllocated { get; private set; }
/// <summary>
/// Gets the average number of sent frames per minute.
/// </summary>
public int AverageSentFramesPerMinute { get; private set; }
/// <summary>
/// Gets the average number of frames that were sent as null per minute.
/// </summary>
public int AverageNulledFramesPerMinute { get; private set; }
/// <summary>
/// Gets the average frame deficit per minute.
/// </summary>
public int AverageDeficitFramesPerMinute { get; private set; }
internal bool Updated;
/// <summary>
/// Initializes a new instance of the <see cref="LavalinkStatistics"/> class.
/// </summary>
internal LavalinkStatistics()
{
this.Updated = false;
}
/// <summary>
/// Updates the stats.
/// </summary>
/// <param name="newStats">The new stats.</param>
internal void Update(LavalinkStats newStats)
{
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/Entities/LavalinkVoiceServerUpdate.cs b/DisCatSharp.Lavalink/Entities/LavalinkVoiceServerUpdate.cs
index f5c50d5aa..678c85db8 100644
--- a/DisCatSharp.Lavalink/Entities/LavalinkVoiceServerUpdate.cs
+++ b/DisCatSharp.Lavalink/Entities/LavalinkVoiceServerUpdate.cs
@@ -1,94 +1,94 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.EventArgs;
using Newtonsoft.Json;
namespace DisCatSharp.Lavalink.Entities;
/// <summary>
/// The lavalink voice server update.
/// </summary>
internal sealed class LavalinkVoiceServerUpdate
{
/// <summary>
/// Gets the token.
/// </summary>
[JsonProperty("token")]
public string Token { get; }
/// <summary>
/// Gets the guild id.
/// </summary>
[JsonProperty("guild_id")]
public string GuildId { get; }
/// <summary>
/// Gets the endpoint.
/// </summary>
[JsonProperty("endpoint")]
public string Endpoint { get; }
/// <summary>
/// Initializes a new instance of the <see cref="LavalinkVoiceServerUpdate"/> class.
/// </summary>
/// <param name="vsu">The vsu.</param>
internal LavalinkVoiceServerUpdate(VoiceServerUpdateEventArgs vsu)
{
this.Token = vsu.VoiceToken;
this.GuildId = vsu.Guild.Id.ToString(CultureInfo.InvariantCulture);
this.Endpoint = vsu.Endpoint;
}
}
/// <summary>
/// The lavalink voice update.
/// </summary>
internal sealed class LavalinkVoiceUpdate : LavalinkPayload
{
/// <summary>
/// Gets the session id.
/// </summary>
[JsonProperty("sessionId")]
public string SessionId { get; }
/// <summary>
/// Gets the event.
/// </summary>
[JsonProperty("event")]
internal LavalinkVoiceServerUpdate Event { get; }
/// <summary>
/// Initializes a new instance of the <see cref="LavalinkVoiceUpdate"/> class.
/// </summary>
/// <param name="vstu">The vstu.</param>
/// <param name="vsrvu">The vsrvu.</param>
public LavalinkVoiceUpdate(VoiceStateUpdateEventArgs vstu, VoiceServerUpdateEventArgs vsrvu)
: base("voiceUpdate", vstu.Guild.Id.ToString(CultureInfo.InvariantCulture))
{
this.SessionId = vstu.SessionId;
this.Event = new LavalinkVoiceServerUpdate(vsrvu);
}
}
diff --git a/DisCatSharp.Lavalink/Entities/LavalinkVoiceStateUpdatePayload.cs b/DisCatSharp.Lavalink/Entities/LavalinkVoiceStateUpdatePayload.cs
index 38a8ba139..39120f25b 100644
--- a/DisCatSharp.Lavalink/Entities/LavalinkVoiceStateUpdatePayload.cs
+++ b/DisCatSharp.Lavalink/Entities/LavalinkVoiceStateUpdatePayload.cs
@@ -1,67 +1,67 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Lavalink.Entities;
/// <summary>
/// The voice state update payload.
/// </summary>
internal sealed class VoiceStateUpdatePayload
{
/// <summary>
/// Gets or sets the guild id.
/// </summary>
[JsonProperty("guild_id")]
public ulong GuildId { get; set; }
/// <summary>
/// Gets or sets the channel id.
/// </summary>
[JsonProperty("channel_id")]
public ulong? ChannelId { get; set; }
/// <summary>
/// Gets or sets the user id.
/// </summary>
[JsonProperty("user_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? UserId { get; set; }
/// <summary>
/// Gets or sets the session id.
/// </summary>
[JsonProperty("session_id", NullValueHandling = NullValueHandling.Ignore)]
public string SessionId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether deafened.
/// </summary>
[JsonProperty("self_deaf")]
public bool Deafened { get; set; }
/// <summary>
/// Gets or sets a value indicating whether muted.
/// </summary>
[JsonProperty("self_mute")]
public bool Muted { get; set; }
}
diff --git a/DisCatSharp.Lavalink/Enums/LavalinkRoutePlannerType.cs b/DisCatSharp.Lavalink/Enums/LavalinkRoutePlannerType.cs
index 6280aaaf6..4c59b6975 100644
--- a/DisCatSharp.Lavalink/Enums/LavalinkRoutePlannerType.cs
+++ b/DisCatSharp.Lavalink/Enums/LavalinkRoutePlannerType.cs
@@ -1,49 +1,49 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Lavalink;
/// <summary>
/// The lavalink route planner type.
/// </summary>
public enum LavalinkRoutePlannerType
{
/// <summary>
/// Route planner that switches the IP on ban.
/// </summary>
RotatingIpRoutePlanner = 1,
/// <summary>
/// Route planner that selects random IP addresses from the given block.
/// </summary>
BalancingIpRoutePlanner = 2,
/// <summary>
/// Route planner that switches the IP on every clock update.
/// </summary>
NanoIpRoutePlanner = 3,
/// <summary>
/// Route planner that switches the IP on every clock update and rotates to next IP block on a ban as a fallback.
/// </summary>
RotatingNanoIpRoutePlanner = 4
}
diff --git a/DisCatSharp.Lavalink/Enums/LavalinkSearchType.cs b/DisCatSharp.Lavalink/Enums/LavalinkSearchType.cs
index 1676a80fb..37f2c7cff 100644
--- a/DisCatSharp.Lavalink/Enums/LavalinkSearchType.cs
+++ b/DisCatSharp.Lavalink/Enums/LavalinkSearchType.cs
@@ -1,54 +1,54 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Lavalink;
/// <summary>
/// The lavalink search type.
/// </summary>
public enum LavalinkSearchType
{
/// <summary>
/// Search on SoundCloud
/// </summary>
SoundCloud,
/// <summary>
/// Search on Youtube.
/// </summary>
Youtube,
/// <summary>
/// Provide Lavalink with a plain URL.
/// </summary>
Plain,
/// <summary>
/// Search on Spotify.
/// </summary>
Spotify,
/// <summary>
/// Search on Apple Music
/// </summary>
AppleMusic
}
diff --git a/DisCatSharp.Lavalink/EventArgs/NodeDisconnectedEventArgs.cs b/DisCatSharp.Lavalink/EventArgs/NodeDisconnectedEventArgs.cs
index a7eab8b50..3a74548c2 100644
--- a/DisCatSharp.Lavalink/EventArgs/NodeDisconnectedEventArgs.cs
+++ b/DisCatSharp.Lavalink/EventArgs/NodeDisconnectedEventArgs.cs
@@ -1,52 +1,52 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.EventArgs;
namespace DisCatSharp.Lavalink.EventArgs;
/// <summary>
/// Represents event arguments for Lavalink node disconnection.
/// </summary>
public sealed class NodeDisconnectedEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the node that was disconnected.
/// </summary>
public LavalinkNodeConnection LavalinkNode { get; }
/// <summary>
/// Gets whether disconnect was clean.
/// </summary>
public bool IsCleanClose { get; }
/// <summary>
/// Initializes a new instance of the <see cref="NodeDisconnectedEventArgs"/> class.
/// </summary>
/// <param name="node">The node.</param>
/// <param name="isClean">If true, is clean.</param>
internal NodeDisconnectedEventArgs(LavalinkNodeConnection node, bool isClean) : base(node.Discord.ServiceProvider)
{
this.LavalinkNode = node;
this.IsCleanClose = isClean;
}
}
diff --git a/DisCatSharp.Lavalink/EventArgs/PlayerUpdateEventArgs.cs b/DisCatSharp.Lavalink/EventArgs/PlayerUpdateEventArgs.cs
index 58c0d541b..d3f07f33b 100644
--- a/DisCatSharp.Lavalink/EventArgs/PlayerUpdateEventArgs.cs
+++ b/DisCatSharp.Lavalink/EventArgs/PlayerUpdateEventArgs.cs
@@ -1,69 +1,69 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.EventArgs;
namespace DisCatSharp.Lavalink.EventArgs;
/// <summary>
/// Represents arguments for player update event.
/// </summary>
public sealed class PlayerUpdateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the timestamp at which this event was emitted.
/// </summary>
public DateTimeOffset Timestamp { get; }
/// <summary>
/// Gets the position in the playback stream.
/// </summary>
public TimeSpan Position { get; }
/// <summary>
/// Gets the player that emitted this event.
/// </summary>
public LavalinkGuildConnection Player { get; }
/// <summary>
/// Gets whether the player is connected to the voice gateway.
/// </summary>
public bool IsConnected { get; }
/// <summary>
/// Initializes a new instance of the <see cref="PlayerUpdateEventArgs"/> class.
/// </summary>
/// <param name="lvl">The lvl.</param>
/// <param name="timestamp">The timestamp.</param>
/// <param name="position">The position.</param>
/// <param name="connected">Whether the player is connected.</param>
internal PlayerUpdateEventArgs(LavalinkGuildConnection lvl, DateTimeOffset timestamp, TimeSpan position, bool connected)
: base(lvl.Node.Discord.ServiceProvider)
{
this.Player = lvl;
this.Timestamp = timestamp;
this.Position = position;
this.IsConnected = connected;
}
}
diff --git a/DisCatSharp.Lavalink/EventArgs/StatisticsReceivedEventArgs.cs b/DisCatSharp.Lavalink/EventArgs/StatisticsReceivedEventArgs.cs
index ffb77e07d..16011cd99 100644
--- a/DisCatSharp.Lavalink/EventArgs/StatisticsReceivedEventArgs.cs
+++ b/DisCatSharp.Lavalink/EventArgs/StatisticsReceivedEventArgs.cs
@@ -1,50 +1,50 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.EventArgs;
using DisCatSharp.Lavalink.Entities;
namespace DisCatSharp.Lavalink.EventArgs;
/// <summary>
/// Represents arguments for Lavalink statistics received.
/// </summary>
public sealed class StatisticsReceivedEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the Lavalink statistics received.
/// </summary>
public LavalinkStatistics Statistics { get; }
/// <summary>
/// Initializes a new instance of the <see cref="StatisticsReceivedEventArgs"/> class.
/// </summary>
/// <param name="provider">Service provider.</param>
/// <param name="stats">The stats.</param>
internal StatisticsReceivedEventArgs(IServiceProvider provider, LavalinkStatistics stats) : base(provider)
{
this.Statistics = stats;
}
}
diff --git a/DisCatSharp.Lavalink/EventArgs/TrackEventArgs.cs b/DisCatSharp.Lavalink/EventArgs/TrackEventArgs.cs
index fa96dbf00..61fe4ffa2 100644
--- a/DisCatSharp.Lavalink/EventArgs/TrackEventArgs.cs
+++ b/DisCatSharp.Lavalink/EventArgs/TrackEventArgs.cs
@@ -1,261 +1,261 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.EventArgs;
namespace DisCatSharp.Lavalink.EventArgs;
/// <summary>
/// The event type.
/// </summary>
internal enum EventType
{
/// <summary>
/// Track start event
/// </summary>
TrackStartEvent,
/// <summary>
/// Track end event
/// </summary>
TrackEndEvent,
/// <summary>
/// Track exception event
/// </summary>
TrackExceptionEvent,
/// <summary>
/// Track stuck event
/// </summary>
TrackStuckEvent,
/// <summary>
/// Websocket closed event
/// </summary>
WebSocketClosedEvent
}
/// <summary>
/// Represents arguments for a track playback start event.
/// </summary>
public sealed class TrackStartEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the track that started playing.
/// </summary>
public LavalinkTrack Track { get; }
/// <summary>
/// Gets the player that started playback.
/// </summary>
public LavalinkGuildConnection Player { get; }
/// <summary>
/// Initializes a new instance of the <see cref="TrackStartEventArgs"/> class.
/// </summary>
/// <param name="lvl">The lvl.</param>
/// <param name="track">The track.</param>
internal TrackStartEventArgs(LavalinkGuildConnection lvl, LavalinkTrack track) : base(lvl.Node.Discord.ServiceProvider)
{
this.Track = track;
this.Player = lvl;
}
}
/// <summary>
/// Represents track finish data
/// </summary>
internal struct TrackFinishData
{
/// <summary>
/// Gets or sets the track.
/// </summary>
public string Track { get; set; }
/// <summary>
/// Gets or sets the reason.
/// </summary>
public TrackEndReason Reason { get; set; }
}
/// <summary>
/// Represents arguments for a track playback finish event.
/// </summary>
public sealed class TrackFinishEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the track that finished playing.
/// </summary>
public LavalinkTrack Track { get; }
/// <summary>
/// Gets the reason why the track stopped playing.
/// </summary>
public TrackEndReason Reason { get; }
/// <summary>
/// Gets the player that finished playback.
/// </summary>
public LavalinkGuildConnection Player { get; }
/// <summary>
/// Initializes a new instance of the <see cref="TrackFinishEventArgs"/> class.
/// </summary>
/// <param name="lvl">The lvl.</param>
/// <param name="track">The track.</param>
/// <param name="reason">The reason.</param>
internal TrackFinishEventArgs(LavalinkGuildConnection lvl, LavalinkTrack track, TrackEndReason reason) : base(lvl.Node.Discord.ServiceProvider)
{
this.Track = track;
this.Reason = reason;
this.Player = lvl;
}
}
/// <summary>
/// Represents a reason why a track finished playing.
/// </summary>
public enum TrackEndReason
{
/// <summary>
/// This means that the track itself emitted a terminator. This is usually caused by the track reaching the end,
/// however it will also be used when it ends due to an exception.
/// </summary>
Finished,
/// <summary>
/// This means that the track failed to start, throwing an exception before providing any audio.
/// </summary>
LoadFailed,
/// <summary>
/// The track was stopped due to the player being stopped by either calling stop() or playTrack(null).
/// </summary>
Stopped,
/// <summary>
/// The track stopped playing because a new track started playing. Note that with this reason, the old track will still
/// play until either its buffer runs out or audio from the new track is available.
/// </summary>
Replaced,
/// <summary>
/// The track was stopped because the cleanup threshold for the audio player was reached. This triggers when the amount
/// of time passed since the last call to AudioPlayer#provide() has reached the threshold specified in player manager
/// configuration. This may also indicate either a leaked audio player which was discarded, but not stopped.
/// </summary>
Cleanup
}
/// <summary>
/// Represents track stuck data
/// </summary>
internal struct TrackStuckData
{
/// <summary>
/// Gets or sets the threshold.
/// </summary>
public long Threshold { get; set; }
/// <summary>
/// Gets or sets the track.
/// </summary>
public string Track { get; set; }
}
/// <summary>
/// Represents event arguments for a track stuck event.
/// </summary>
public sealed class TrackStuckEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the millisecond threshold for the stuck event.
/// </summary>
public long ThresholdMilliseconds { get; }
/// <summary>
/// Gets the track that got stuck.
/// </summary>
public LavalinkTrack Track { get; }
/// <summary>
/// Gets the player that got stuck.
/// </summary>
public LavalinkGuildConnection Player { get; }
/// <summary>
/// Initializes a new instance of the <see cref="TrackStuckEventArgs"/> class.
/// </summary>
/// <param name="lvl">The lvl.</param>
/// <param name="thresholdMs">The threshold ms.</param>
/// <param name="track">The track.</param>
internal TrackStuckEventArgs(LavalinkGuildConnection lvl, long thresholdMs, LavalinkTrack track) : base(lvl.Node.Discord.ServiceProvider)
{
this.ThresholdMilliseconds = thresholdMs;
this.Track = track;
this.Player = lvl;
}
}
/// <summary>
/// Represents track exception data
/// </summary>
internal struct TrackExceptionData
{
/// <summary>
/// Gets or sets the error.
/// </summary>
public string Error { get; set; }
/// <summary>
/// Gets or sets the track.
/// </summary>
public string Track { get; set; }
}
/// <summary>
/// Represents event arguments for a track exception event.
/// </summary>
public sealed class TrackExceptionEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the exception that occurred during playback.
/// </summary>
public LavalinkLoadFailedInfo Exception { get; }
/// <summary>
/// Gets the track that got stuck.
/// </summary>
public LavalinkTrack Track { get; }
/// <summary>
/// Gets the player that got stuck.
/// </summary>
public LavalinkGuildConnection Player { get; }
/// <summary>
/// Initializes a new instance of the <see cref="TrackExceptionEventArgs"/> class.
/// </summary>
/// <param name="lvl">The lvl.</param>
/// <param name="exception">The exception.</param>
/// <param name="track">The track.</param>
internal TrackExceptionEventArgs(LavalinkGuildConnection lvl, LavalinkLoadFailedInfo exception, LavalinkTrack track) : base(lvl.Node.Discord.ServiceProvider)
{
this.Exception = exception;
this.Track = track;
this.Player = lvl;
}
}
diff --git a/DisCatSharp.Lavalink/EventArgs/WebSocketCloseEventArgs.cs b/DisCatSharp.Lavalink/EventArgs/WebSocketCloseEventArgs.cs
index 3cddb6b0c..e1bc64a00 100644
--- a/DisCatSharp.Lavalink/EventArgs/WebSocketCloseEventArgs.cs
+++ b/DisCatSharp.Lavalink/EventArgs/WebSocketCloseEventArgs.cs
@@ -1,62 +1,62 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.EventArgs;
namespace DisCatSharp.Lavalink.EventArgs;
/// <summary>
/// Represents arguments for <see cref="LavalinkGuildConnection.DiscordWebSocketClosed"/> event.
/// </summary>
public sealed class WebSocketCloseEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the WebSocket close code.
/// </summary>
public int Code { get; }
/// <summary>
/// Gets the WebSocket close reason.
/// </summary>
public string Reason { get; }
/// <summary>
/// Gets whether the termination was initiated by the remote party (i.e. Discord).
/// </summary>
public bool Remote { get; }
/// <summary>
/// Initializes a new instance of the <see cref="WebSocketCloseEventArgs"/> class.
/// </summary>
/// <param name="code">The code.</param>
/// <param name="reason">The reason.</param>
/// <param name="remote">If true, remote.</param>
/// <param name="provider">Service provider.</param>
internal WebSocketCloseEventArgs(int code, string reason, bool remote, IServiceProvider provider) : base(provider)
{
this.Code = code;
this.Reason = reason;
this.Remote = remote;
}
}
diff --git a/DisCatSharp.Lavalink/GlobalSuppressions.cs b/DisCatSharp.Lavalink/GlobalSuppressions.cs
index 8e5e316a6..3614351e6 100644
--- a/DisCatSharp.Lavalink/GlobalSuppressions.cs
+++ b/DisCatSharp.Lavalink/GlobalSuppressions.cs
@@ -1,34 +1,34 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Lavalink.LavalinkGuildConnection.#ctor(DisCatSharp.Lavalink.LavalinkNodeConnection,DisCatSharp.Entities.DiscordChannel,DisCatSharp.EventArgs.VoiceStateUpdateEventArgs)")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Lavalink.LavalinkNodeConnection.StartAsync~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Lavalink.LavalinkNodeConnection.WebSocket_OnMessage(DisCatSharp.Net.WebSocket.IWebSocketClient,DisCatSharp.EventArgs.SocketMessageEventArgs)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Lavalink.LavalinkNodeConnection.WsSendAsync(System.String)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Lavalink.LavalinkNodeConnection.WebSocket_OnDisconnect(DisCatSharp.Net.WebSocket.IWebSocketClient,DisCatSharp.EventArgs.SocketCloseEventArgs)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Lavalink.LavalinkRestClient.InternalDecodeTrackAsync(System.Uri)~System.Threading.Tasks.Task{DisCatSharp.Lavalink.LavalinkTrack}")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Lavalink.LavalinkRestClient.InternalFreeAddressAsync(System.Uri,System.String)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Lavalink.LavalinkRestClient.InternalFreeAllAddressesAsync(System.Uri)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Lavalink.Entities.LavalinkRouteStatus.GetLavalinkRoutePlannerType(System.String)~System.Nullable{DisCatSharp.Lavalink.LavalinkRoutePlannerType}")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Lavalink.LavalinkExtension.FilterByLoad(DisCatSharp.Lavalink.LavalinkNodeConnection[])~DisCatSharp.Lavalink.LavalinkNodeConnection")]
diff --git a/DisCatSharp.Lavalink/LavalinkConfiguration.cs b/DisCatSharp.Lavalink/LavalinkConfiguration.cs
index 3182d62d0..5c1601a4e 100644
--- a/DisCatSharp.Lavalink/LavalinkConfiguration.cs
+++ b/DisCatSharp.Lavalink/LavalinkConfiguration.cs
@@ -1,115 +1,115 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.Net;
using Microsoft.Extensions.DependencyInjection;
namespace DisCatSharp.Lavalink;
/// <summary>
/// Lavalink connection configuration.
/// </summary>
public sealed class LavalinkConfiguration
{
/// <summary>
/// Sets the endpoint for Lavalink REST.
/// <para>Defaults to 127.0.0.1 on port 2333.</para>
/// </summary>
public ConnectionEndpoint RestEndpoint { internal get; set; } = new("127.0.0.1", 2333);
/// <summary>
/// Sets the endpoint for the Lavalink Websocket connection.
/// <para>Defaults to 127.0.0.1 on port 2333.</para>
/// </summary>
public ConnectionEndpoint SocketEndpoint { internal get; set; } = new("127.0.0.1", 2333);
/// <summary>
/// Sets whether the connection wrapper should attempt automatic reconnects should the connection drop.
/// <para>Defaults to true.</para>
/// </summary>
public bool SocketAutoReconnect { internal get; set; } = true;
/// <summary>
/// Sets the password for the Lavalink connection.
/// <para>Defaults to "youshallnotpass".</para>
/// </summary>
public string Password { internal get; set; } = "youshallnotpass";
/// <summary>
/// Sets the resume key for the Lavalink connection.
/// <para>This will allow existing voice sessions to continue for a certain time after the client is disconnected.</para>
/// </summary>
public string ResumeKey { internal get; set; }
/// <summary>
/// Sets the time in seconds when all voice sessions are closed after the client disconnects.
/// <para>Defaults to 60 seconds.</para>
/// </summary>
public int ResumeTimeout { internal get; set; } = 60;
/// <summary>
/// Sets the time in milliseconds to wait for Lavalink's voice WebSocket to close after leaving a voice channel.
/// <para>This will be the delay before the guild connection is removed.</para>
/// <para>Defaults to 3000 milliseconds.</para>
/// </summary>
public int WebSocketCloseTimeout { internal get; set; } = 3000;
/// <summary>
/// Sets the voice region ID for the Lavalink connection.
/// <para>This should be used if nodes should be filtered by region with <see cref="LavalinkExtension.GetIdealNodeConnection(DiscordVoiceRegion)"/>.</para>
/// </summary>
public DiscordVoiceRegion Region { internal get; set; }
/// <summary>
/// Creates a new instance of <see cref="LavalinkConfiguration"/>.
/// </summary>
[ActivatorUtilitiesConstructor]
public LavalinkConfiguration() { }
/// <summary>
/// Creates a new instance of <see cref="LavalinkConfiguration"/>, copying the properties of another configuration.
/// </summary>
/// <param name="other">Configuration the properties of which are to be copied.</param>
public LavalinkConfiguration(LavalinkConfiguration other)
{
this.RestEndpoint = new ConnectionEndpoint
{
Hostname = other.RestEndpoint.Hostname,
Port = other.RestEndpoint.Port,
Secured = other.RestEndpoint.Secured
};
this.SocketEndpoint = new ConnectionEndpoint
{
Hostname = other.SocketEndpoint.Hostname,
Port = other.SocketEndpoint.Port,
Secured = other.SocketEndpoint.Secured
};
this.Password = other.Password;
this.ResumeKey = other.ResumeKey;
this.ResumeTimeout = other.ResumeTimeout;
this.SocketAutoReconnect = other.SocketAutoReconnect;
this.Region = other.Region;
this.WebSocketCloseTimeout = other.WebSocketCloseTimeout;
}
}
diff --git a/DisCatSharp.Lavalink/LavalinkEvents.cs b/DisCatSharp.Lavalink/LavalinkEvents.cs
index e094fda15..9f5c0bcbf 100644
--- a/DisCatSharp.Lavalink/LavalinkEvents.cs
+++ b/DisCatSharp.Lavalink/LavalinkEvents.cs
@@ -1,76 +1,76 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 Microsoft.Extensions.Logging;
namespace DisCatSharp.Lavalink;
/// <summary>
/// Contains well-defined event IDs used by the Lavalink extension.
/// </summary>
public static class LavalinkEvents
{
/// <summary>
/// Miscellaneous events, that do not fit in any other category.
/// </summary>
public static EventId Misc { get; } = new(400, "Lavalink");
/// <summary>
/// Events pertaining to Lavalink node connection errors.
/// </summary>
public static EventId LavalinkConnectionError { get; } = new(401, nameof(LavalinkConnectionError));
/// <summary>
/// Events emitted for clean disconnects from Lavalink.
/// </summary>
public static EventId LavalinkConnectionClosed { get; } = new(402, nameof(LavalinkConnectionClosed));
/// <summary>
/// Events emitted for successful connections made to Lavalink.
/// </summary>
public static EventId LavalinkConnected { get; } = new(403, nameof(LavalinkConnected));
/// <summary>
/// Events pertaining to errors that occur when decoding payloads received from Lavalink nodes.
/// </summary>
public static EventId LavalinkDecodeError { get; } = new(404, nameof(LavalinkDecodeError));
/// <summary>
/// Events emitted when Lavalink's REST API responds with an error.
/// </summary>
public static EventId LavalinkRestError { get; } = new(405, nameof(LavalinkRestError));
/// <summary>
/// Events containing raw payloads, received from Lavalink nodes.
/// </summary>
public static EventId LavalinkWsRx { get; } = new(406, "Lavalink ↓");
/// <summary>
/// Events containing raw payloads, as they're being sent to Lavalink nodes.
/// </summary>
public static EventId LavalinkWsTx { get; } = new(407, "Lavalink ↑");
/// <summary>
/// Events pertaining to Gateway Intents. Typically diagnostic information.
/// </summary>
public static EventId Intents { get; } = new(408, nameof(Intents));
}
diff --git a/DisCatSharp.Lavalink/LavalinkExtension.cs b/DisCatSharp.Lavalink/LavalinkExtension.cs
index 7f2159754..c6757898b 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-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// The lavalink extension.
/// </summary>
public sealed class LavalinkExtension : BaseExtension
{
/// <summary>
/// Triggered whenever a node disconnects.
/// </summary>
public event AsyncEventHandler<LavalinkNodeConnection, NodeDisconnectedEventArgs> NodeDisconnected
{
add => this._nodeDisconnected.Register(value);
remove => this._nodeDisconnected.Unregister(value);
}
private AsyncEvent<LavalinkNodeConnection, NodeDisconnectedEventArgs> _nodeDisconnected;
/// <summary>
/// Gets a dictionary of connected Lavalink nodes for the extension.
/// </summary>
public IReadOnlyDictionary<ConnectionEndpoint, LavalinkNodeConnection> ConnectedNodes { get; }
private readonly ConcurrentDictionary<ConnectionEndpoint, LavalinkNodeConnection> _connectedNodes = new();
/// <summary>
/// Creates a new instance of this Lavalink extension.
/// </summary>
internal LavalinkExtension()
{
this.ConnectedNodes = new ReadOnlyConcurrentDictionary<ConnectionEndpoint, LavalinkNodeConnection>(this._connectedNodes);
}
/// <summary>
/// DO NOT USE THIS MANUALLY.
/// </summary>
/// <param name="client">DO NOT USE THIS MANUALLY.</param>
/// <exception cref="InvalidOperationException"/>
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<LavalinkNodeConnection, NodeDisconnectedEventArgs>("LAVALINK_NODE_DISCONNECTED", TimeSpan.Zero, this.Client.EventErrorHandler);
}
/// <summary>
/// Connect to a Lavalink node.
/// </summary>
/// <param name="config">Lavalink client configuration.</param>
/// <returns>The established Lavalink connection.</returns>
public async Task<LavalinkNodeConnection> 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;
}
/// <summary>
/// Gets the Lavalink node connection for the specified endpoint.
/// </summary>
/// <param name="endpoint">Endpoint at which the node resides.</param>
/// <returns>Lavalink node connection.</returns>
public LavalinkNodeConnection GetNodeConnection(ConnectionEndpoint endpoint)
=> this._connectedNodes.ContainsKey(endpoint) ? this._connectedNodes[endpoint] : null;
/// <summary>
/// Gets a Lavalink node connection based on load balancing and an optional voice region.
/// </summary>
/// <param name="region">The region to compare with the node's <see cref="LavalinkConfiguration.Region"/>, if any.</param>
/// <returns>The least load affected node connection, or null if no nodes are present.</returns>
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<LavalinkNodeConnection, bool>(x => x.Region == region);
if (nodes.Any(regionPredicate))
nodes = nodes.Where(regionPredicate).ToArray();
if (nodes.Length <= 1)
return nodes.FirstOrDefault();
}
return this.FilterByLoad(nodes);
}
/// <summary>
/// Gets a Lavalink guild connection from a <see cref="DisCatSharp.Entities.DiscordGuild"/>.
/// </summary>
/// <param name="guild">The guild the connection is on.</param>
/// <returns>The found guild connection, or null if one could not be found.</returns>
public LavalinkGuildConnection GetGuildConnection(DiscordGuild guild)
{
var nodes = this._connectedNodes.Values;
var node = nodes.FirstOrDefault(x => x.ConnectedGuildsInternal.ContainsKey(guild.Id));
return node?.GetGuildConnection(guild);
}
/// <summary>
/// Filters the by load.
/// </summary>
/// <param name="nodes">The nodes.</param>
private LavalinkNodeConnection FilterByLoad(LavalinkNodeConnection[] nodes)
{
Array.Sort(nodes, (a, b) =>
{
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];
}
/// <summary>
/// Removes a node.
/// </summary>
/// <param name="node">The node to be removed.</param>
private void Con_NodeDisconnected(LavalinkNodeConnection node)
=> this._connectedNodes.TryRemove(node.NodeEndpoint, out _);
/// <summary>
/// Disconnects a node.
/// </summary>
/// <param name="node">The affected node.</param>
/// <param name="e">The node disconnected event args.</param>
private Task Con_Disconnected(LavalinkNodeConnection node, NodeDisconnectedEventArgs e)
=> this._nodeDisconnected.InvokeAsync(node, e);
}
diff --git a/DisCatSharp.Lavalink/LavalinkGuildConnection.cs b/DisCatSharp.Lavalink/LavalinkGuildConnection.cs
index d86263d13..4969a89a7 100644
--- a/DisCatSharp.Lavalink/LavalinkGuildConnection.cs
+++ b/DisCatSharp.Lavalink/LavalinkGuildConnection.cs
@@ -1,442 +1,442 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.IO;
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 Newtonsoft.Json;
namespace DisCatSharp.Lavalink;
internal delegate void ChannelDisconnectedEventHandler(LavalinkGuildConnection node);
/// <summary>
/// Represents a Lavalink connection to a channel.
/// </summary>
public sealed class LavalinkGuildConnection
{
/// <summary>
/// Triggered whenever Lavalink updates player status.
/// </summary>
public event AsyncEventHandler<LavalinkGuildConnection, PlayerUpdateEventArgs> PlayerUpdated
{
add => this._playerUpdated.Register(value);
remove => this._playerUpdated.Unregister(value);
}
private readonly AsyncEvent<LavalinkGuildConnection, PlayerUpdateEventArgs> _playerUpdated;
/// <summary>
/// Triggered whenever playback of a track starts.
/// <para>This is only available for version 3.3.1 and greater.</para>
/// </summary>
public event AsyncEventHandler<LavalinkGuildConnection, TrackStartEventArgs> PlaybackStarted
{
add => this._playbackStarted.Register(value);
remove => this._playbackStarted.Unregister(value);
}
private readonly AsyncEvent<LavalinkGuildConnection, TrackStartEventArgs> _playbackStarted;
/// <summary>
/// Triggered whenever playback of a track finishes.
/// </summary>
public event AsyncEventHandler<LavalinkGuildConnection, TrackFinishEventArgs> PlaybackFinished
{
add => this._playbackFinished.Register(value);
remove => this._playbackFinished.Unregister(value);
}
private readonly AsyncEvent<LavalinkGuildConnection, TrackFinishEventArgs> _playbackFinished;
/// <summary>
/// Triggered whenever playback of a track gets stuck.
/// </summary>
public event AsyncEventHandler<LavalinkGuildConnection, TrackStuckEventArgs> TrackStuck
{
add => this._trackStuck.Register(value);
remove => this._trackStuck.Unregister(value);
}
private readonly AsyncEvent<LavalinkGuildConnection, TrackStuckEventArgs> _trackStuck;
/// <summary>
/// Triggered whenever playback of a track encounters an error.
/// </summary>
public event AsyncEventHandler<LavalinkGuildConnection, TrackExceptionEventArgs> TrackException
{
add => this._trackException.Register(value);
remove => this._trackException.Unregister(value);
}
private readonly AsyncEvent<LavalinkGuildConnection, TrackExceptionEventArgs> _trackException;
/// <summary>
/// Triggered whenever Discord Voice WebSocket connection is terminated.
/// </summary>
public event AsyncEventHandler<LavalinkGuildConnection, WebSocketCloseEventArgs> DiscordWebSocketClosed
{
add => this._webSocketClosed.Register(value);
remove => this._webSocketClosed.Unregister(value);
}
private readonly AsyncEvent<LavalinkGuildConnection, WebSocketCloseEventArgs> _webSocketClosed;
/// <summary>
/// Gets whether this channel is still connected.
/// </summary>
public bool IsConnected => !Volatile.Read(ref this._isDisposed) && this.Channel != null;
private bool _isDisposed;
/// <summary>
/// Gets the current player state.
/// </summary>
public LavalinkPlayerState CurrentState { get; }
/// <summary>
/// Gets the voice channel associated with this connection.
/// </summary>
public DiscordChannel Channel => this.VoiceStateUpdate.Channel;
/// <summary>
/// Gets the guild associated with this connection.
/// </summary>
public DiscordGuild Guild => this.Channel.Guild;
/// <summary>
/// Gets the Lavalink node associated with this connection.
/// </summary>
public LavalinkNodeConnection Node { get; }
/// <summary>
/// Gets the guild id string.
/// </summary>
internal string GuildIdString => this.GuildId.ToString(CultureInfo.InvariantCulture);
/// <summary>
/// Gets the guild id.
/// </summary>
internal ulong GuildId => this.Channel.Guild.Id;
/// <summary>
/// Gets or sets the voice state update.
/// </summary>
internal VoiceStateUpdateEventArgs VoiceStateUpdate { get; set; }
/// <summary>
/// Gets or sets the voice ws disconnect tcs.
/// </summary>
internal TaskCompletionSource<bool> VoiceWsDisconnectTcs { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="LavalinkGuildConnection"/> class.
/// </summary>
/// <param name="node">The node.</param>
/// <param name="channel">The channel.</param>
/// <param name="vstu">The vstu.</param>
internal LavalinkGuildConnection(LavalinkNodeConnection node, DiscordChannel channel, VoiceStateUpdateEventArgs vstu)
{
this.Node = node;
this.VoiceStateUpdate = vstu;
this.CurrentState = new LavalinkPlayerState();
this.VoiceWsDisconnectTcs = new TaskCompletionSource<bool>();
Volatile.Write(ref this._isDisposed, false);
this._playerUpdated = new AsyncEvent<LavalinkGuildConnection, PlayerUpdateEventArgs>("LAVALINK_PLAYER_UPDATE", TimeSpan.Zero, this.Node.Discord.EventErrorHandler);
this._playbackStarted = new AsyncEvent<LavalinkGuildConnection, TrackStartEventArgs>("LAVALINK_PLAYBACK_STARTED", TimeSpan.Zero, this.Node.Discord.EventErrorHandler);
this._playbackFinished = new AsyncEvent<LavalinkGuildConnection, TrackFinishEventArgs>("LAVALINK_PLAYBACK_FINISHED", TimeSpan.Zero, this.Node.Discord.EventErrorHandler);
this._trackStuck = new AsyncEvent<LavalinkGuildConnection, TrackStuckEventArgs>("LAVALINK_TRACK_STUCK", TimeSpan.Zero, this.Node.Discord.EventErrorHandler);
this._trackException = new AsyncEvent<LavalinkGuildConnection, TrackExceptionEventArgs>("LAVALINK_TRACK_EXCEPTION", TimeSpan.Zero, this.Node.Discord.EventErrorHandler);
this._webSocketClosed = new AsyncEvent<LavalinkGuildConnection, WebSocketCloseEventArgs>("LAVALINK_DISCORD_WEBSOCKET_CLOSED", TimeSpan.Zero, this.Node.Discord.EventErrorHandler);
}
/// <summary>
/// Disconnects the connection from the voice channel.
/// </summary>
/// <param name="shouldDestroy">Whether the connection should be destroyed on the Lavalink server when leaving.</param>
public Task DisconnectAsync(bool shouldDestroy = true)
=> this.DisconnectInternalAsync(shouldDestroy);
/// <summary>
/// Disconnects the internal async.
/// </summary>
/// <param name="shouldDestroy">If true, should destroy.</param>
/// <param name="isManualDisconnection">If true, is manual disconnection.</param>
internal async Task DisconnectInternalAsync(bool shouldDestroy, bool isManualDisconnection = false)
{
if (!this.IsConnected && !isManualDisconnection)
throw new InvalidOperationException("This connection is not valid.");
Volatile.Write(ref this._isDisposed, true);
if (shouldDestroy)
await this.Node.SendPayloadAsync(new LavalinkDestroy(this)).ConfigureAwait(false);
if (!isManualDisconnection)
{
await this.SendVoiceUpdateAsync().ConfigureAwait(false);
this.ChannelDisconnected?.Invoke(this);
}
}
/// <summary>
/// Sends the voice update async.
/// </summary>
internal async Task SendVoiceUpdateAsync()
{
var vsd = new VoiceDispatch
{
OpCode = 4,
Payload = new VoiceStateUpdatePayload
{
GuildId = this.GuildId,
ChannelId = null,
Deafened = false,
Muted = false
}
};
var vsj = JsonConvert.SerializeObject(vsd, Formatting.None);
await (this.Channel.Discord as DiscordClient).WsSendAsync(vsj).ConfigureAwait(false);
}
/// <summary>
/// Searches for specified terms.
/// </summary>
/// <param name="searchQuery">What to search for.</param>
/// <param name="type">What platform will search for.</param>
/// <returns>A collection of tracks matching the criteria.</returns>
public Task<LavalinkLoadResult> GetTracksAsync(string searchQuery, LavalinkSearchType type = LavalinkSearchType.Youtube)
=> this.Node.Rest.GetTracksAsync(searchQuery, type);
/// <summary>
/// Loads tracks from specified URL.
/// </summary>
/// <param name="uri">URL to load tracks from.</param>
/// <returns>A collection of tracks from the URL.</returns>
public Task<LavalinkLoadResult> GetTracksAsync(Uri uri)
=> this.Node.Rest.GetTracksAsync(uri);
/// <summary>
/// Loads tracks from a local file.
/// </summary>
/// <param name="file">File to load tracks from.</param>
/// <returns>A collection of tracks from the file.</returns>
public Task<LavalinkLoadResult> GetTracksAsync(FileInfo file)
=> this.Node.Rest.GetTracksAsync(file);
/// <summary>
/// Queues the specified track for playback.
/// </summary>
/// <param name="track">Track to play.</param>
public async Task PlayAsync(LavalinkTrack track)
{
if (!this.IsConnected)
throw new InvalidOperationException("This connection is not valid.");
this.CurrentState.CurrentTrack = track;
await this.Node.SendPayloadAsync(new LavalinkPlay(this, track)).ConfigureAwait(false);
}
/// <summary>
/// Queues the specified track for playback. The track will be played from specified start timestamp to specified end timestamp.
/// </summary>
/// <param name="track">Track to play.</param>
/// <param name="start">Timestamp to start playback at.</param>
/// <param name="end">Timestamp to stop playback at.</param>
public async Task PlayPartialAsync(LavalinkTrack track, TimeSpan start, TimeSpan end)
{
if (!this.IsConnected)
throw new InvalidOperationException("This connection is not valid.");
if (start.TotalMilliseconds < 0 || end <= start)
throw new ArgumentException("Both start and end timestamps need to be greater or equal to zero, and the end timestamp needs to be greater than start timestamp.");
this.CurrentState.CurrentTrack = track;
await this.Node.SendPayloadAsync(new LavalinkPlayPartial(this, track, start, end)).ConfigureAwait(false);
}
/// <summary>
/// Stops the player completely.
/// </summary>
public async Task StopAsync()
{
if (!this.IsConnected)
throw new InvalidOperationException("This connection is not valid.");
await this.Node.SendPayloadAsync(new LavalinkStop(this)).ConfigureAwait(false);
}
/// <summary>
/// Pauses the player.
/// </summary>
public async Task PauseAsync()
{
if (!this.IsConnected)
throw new InvalidOperationException("This connection is not valid.");
await this.Node.SendPayloadAsync(new LavalinkPause(this, true)).ConfigureAwait(false);
}
/// <summary>
/// Resumes playback.
/// </summary>
public async Task ResumeAsync()
{
if (!this.IsConnected)
throw new InvalidOperationException("This connection is not valid.");
await this.Node.SendPayloadAsync(new LavalinkPause(this, false)).ConfigureAwait(false);
}
/// <summary>
/// Seeks the current track to specified position.
/// </summary>
/// <param name="position">Position to seek to.</param>
public async Task SeekAsync(TimeSpan position)
{
if (!this.IsConnected)
throw new InvalidOperationException("This connection is not valid.");
await this.Node.SendPayloadAsync(new LavalinkSeek(this, position)).ConfigureAwait(false);
}
/// <summary>
/// Sets the playback volume. This might incur a lot of CPU usage.
/// </summary>
/// <param name="volume">Volume to set. Needs to be greater or equal to 0, and less than or equal to 100. 100 means 100% and is the default value.</param>
public async Task SetVolumeAsync(int volume)
{
if (!this.IsConnected)
throw new InvalidOperationException("This connection is not valid.");
if (volume < 0 || volume > 1000)
throw new ArgumentOutOfRangeException(nameof(volume), "Volume needs to range from 0 to 1000.");
await this.Node.SendPayloadAsync(new LavalinkVolume(this, volume)).ConfigureAwait(false);
}
/// <summary>
/// Adjusts the specified bands in the audio equalizer. This will alter the sound output, and might incur a lot of CPU usage.
/// </summary>
/// <param name="bands">Bands adjustments to make. You must specify one adjustment per band at most.</param>
public async Task AdjustEqualizerAsync(params LavalinkBandAdjustment[] bands)
{
if (!this.IsConnected)
throw new InvalidOperationException("This connection is not valid.");
if (bands?.Any() != true)
return;
if (bands.Distinct(new LavalinkBandAdjustmentComparer()).Count() != bands.Length)
throw new InvalidOperationException("You cannot specify multiple modifiers for the same band.");
await this.Node.SendPayloadAsync(new LavalinkEqualizer(this, bands)).ConfigureAwait(false);
}
/// <summary>
/// Resets the audio equalizer to default values.
/// </summary>
public async Task ResetEqualizerAsync()
{
if (!this.IsConnected)
throw new InvalidOperationException("This connection is not valid.");
await this.Node.SendPayloadAsync(new LavalinkEqualizer(this, Enumerable.Range(0, 15).Select(x => new LavalinkBandAdjustment(x, 0)))).ConfigureAwait(false);
}
/// <summary>
/// Internals the update player state async.
/// </summary>
/// <param name="newState">The new state.</param>
internal Task InternalUpdatePlayerStateAsync(LavalinkState newState)
{
this.CurrentState.LastUpdate = newState.Time;
this.CurrentState.PlaybackPosition = newState.Position;
this.CurrentState.IsConnected = newState.IsConnected;
return this._playerUpdated.InvokeAsync(this, new PlayerUpdateEventArgs(this, newState.Time, newState.Position, newState.IsConnected));
}
/// <summary>
/// Internals the playback started async.
/// </summary>
/// <param name="track">The track.</param>
internal Task InternalPlaybackStartedAsync(string track)
{
var ea = new TrackStartEventArgs(this, LavalinkUtilities.DecodeTrack(track));
return this._playbackStarted.InvokeAsync(this, ea);
}
/// <summary>
/// Internals the playback finished async.
/// </summary>
/// <param name="e">The e.</param>
internal Task InternalPlaybackFinishedAsync(TrackFinishData e)
{
if (e.Reason != TrackEndReason.Replaced)
this.CurrentState.CurrentTrack = default;
var ea = new TrackFinishEventArgs(this, LavalinkUtilities.DecodeTrack(e.Track), e.Reason);
return this._playbackFinished.InvokeAsync(this, ea);
}
/// <summary>
/// Internals the track stuck async.
/// </summary>
/// <param name="e">The e.</param>
internal Task InternalTrackStuckAsync(TrackStuckData e)
{
var ea = new TrackStuckEventArgs(this, e.Threshold, LavalinkUtilities.DecodeTrack(e.Track));
return this._trackStuck.InvokeAsync(this, ea);
}
/// <summary>
/// Internals the track exception async.
/// </summary>
/// <param name="e">The e.</param>
/// <param name="track">The failed track.</param>
internal Task InternalTrackExceptionAsync(LavalinkLoadFailedInfo e, string track)
{
var ea = new TrackExceptionEventArgs(this, e, LavalinkUtilities.DecodeTrack(track));
return this._trackException.InvokeAsync(this, ea);
}
/// <summary>
/// Internals the web socket closed async.
/// </summary>
/// <param name="e">The e.</param>
internal Task InternalWebSocketClosedAsync(WebSocketCloseEventArgs e)
=> this._webSocketClosed.InvokeAsync(this, e);
internal event ChannelDisconnectedEventHandler ChannelDisconnected;
}
diff --git a/DisCatSharp.Lavalink/LavalinkNodeConnection.cs b/DisCatSharp.Lavalink/LavalinkNodeConnection.cs
index ded9d142b..d26f55d8b 100644
--- a/DisCatSharp.Lavalink/LavalinkNodeConnection.cs
+++ b/DisCatSharp.Lavalink/LavalinkNodeConnection.cs
@@ -1,626 +1,626 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
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);
/// <summary>
/// Represents a connection to a Lavalink node.
/// </summary>
public sealed class LavalinkNodeConnection
{
/// <summary>
/// Triggered whenever Lavalink WebSocket throws an exception.
/// </summary>
public event AsyncEventHandler<LavalinkNodeConnection, SocketErrorEventArgs> LavalinkSocketErrored
{
add => this._lavalinkSocketError.Register(value);
remove => this._lavalinkSocketError.Unregister(value);
}
private readonly AsyncEvent<LavalinkNodeConnection, SocketErrorEventArgs> _lavalinkSocketError;
/// <summary>
/// Triggered when this node disconnects.
/// </summary>
public event AsyncEventHandler<LavalinkNodeConnection, NodeDisconnectedEventArgs> Disconnected
{
add => this._disconnected.Register(value);
remove => this._disconnected.Unregister(value);
}
private readonly AsyncEvent<LavalinkNodeConnection, NodeDisconnectedEventArgs> _disconnected;
/// <summary>
/// Triggered when this node receives a statistics update.
/// </summary>
public event AsyncEventHandler<LavalinkNodeConnection, StatisticsReceivedEventArgs> StatisticsReceived
{
add => this._statsReceived.Register(value);
remove => this._statsReceived.Unregister(value);
}
private readonly AsyncEvent<LavalinkNodeConnection, StatisticsReceivedEventArgs> _statsReceived;
/// <summary>
/// Triggered whenever any of the players on this node is updated.
/// </summary>
public event AsyncEventHandler<LavalinkGuildConnection, PlayerUpdateEventArgs> PlayerUpdated
{
add => this._playerUpdated.Register(value);
remove => this._playerUpdated.Unregister(value);
}
private readonly AsyncEvent<LavalinkGuildConnection, PlayerUpdateEventArgs> _playerUpdated;
/// <summary>
/// Triggered whenever playback of a track starts.
/// <para>This is only available for version 3.3.1 and greater.</para>
/// </summary>
public event AsyncEventHandler<LavalinkGuildConnection, TrackStartEventArgs> PlaybackStarted
{
add => this._playbackStarted.Register(value);
remove => this._playbackStarted.Unregister(value);
}
private readonly AsyncEvent<LavalinkGuildConnection, TrackStartEventArgs> _playbackStarted;
/// <summary>
/// Triggered whenever playback of a track finishes.
/// </summary>
public event AsyncEventHandler<LavalinkGuildConnection, TrackFinishEventArgs> PlaybackFinished
{
add => this._playbackFinished.Register(value);
remove => this._playbackFinished.Unregister(value);
}
private readonly AsyncEvent<LavalinkGuildConnection, TrackFinishEventArgs> _playbackFinished;
/// <summary>
/// Triggered whenever playback of a track gets stuck.
/// </summary>
public event AsyncEventHandler<LavalinkGuildConnection, TrackStuckEventArgs> TrackStuck
{
add => this._trackStuck.Register(value);
remove => this._trackStuck.Unregister(value);
}
private readonly AsyncEvent<LavalinkGuildConnection, TrackStuckEventArgs> _trackStuck;
/// <summary>
/// Triggered whenever playback of a track encounters an error.
/// </summary>
public event AsyncEventHandler<LavalinkGuildConnection, TrackExceptionEventArgs> TrackException
{
add => this._trackException.Register(value);
remove => this._trackException.Unregister(value);
}
private readonly AsyncEvent<LavalinkGuildConnection, TrackExceptionEventArgs> _trackException;
/// <summary>
/// Gets the remote endpoint of this Lavalink node connection.
/// </summary>
public ConnectionEndpoint NodeEndpoint => this.Configuration.SocketEndpoint;
/// <summary>
/// Gets whether the client is connected to Lavalink.
/// </summary>
public bool IsConnected => !Volatile.Read(ref this._isDisposed);
private bool _isDisposed;
private int _backoff;
/// <summary>
/// The minimum backoff.
/// </summary>
private const int MINIMUM_BACKOFF = 7500;
/// <summary>
/// The maximum backoff.
/// </summary>
private const int MAXIMUM_BACKOFF = 120000;
/// <summary>
/// Gets the current resource usage statistics.
/// </summary>
public LavalinkStatistics Statistics { get; }
/// <summary>
/// Gets a dictionary of Lavalink guild connections for this node.
/// </summary>
public IReadOnlyDictionary<ulong, LavalinkGuildConnection> ConnectedGuilds { get; }
internal ConcurrentDictionary<ulong, LavalinkGuildConnection> ConnectedGuildsInternal = new();
/// <summary>
/// Gets the REST client for this Lavalink connection.
/// </summary>
public LavalinkRestClient Rest { get; }
/// <summary>
/// Gets the parent extension which this node connection belongs to.
/// </summary>
public LavalinkExtension Parent { get; }
/// <summary>
/// Gets the Discord client this node connection belongs to.
/// </summary>
public DiscordClient Discord { get; }
/// <summary>
/// Gets the configuration.
/// </summary>
internal LavalinkConfiguration Configuration { get; }
/// <summary>
/// Gets the region.
/// </summary>
internal DiscordVoiceRegion Region { get; }
/// <summary>
/// Gets or sets the web socket.
/// </summary>
private IWebSocketClient _webSocket;
/// <summary>
/// Gets the voice state updates.
/// </summary>
private readonly ConcurrentDictionary<ulong, TaskCompletionSource<VoiceStateUpdateEventArgs>> _voiceStateUpdates;
/// <summary>
/// Gets the voice server updates.
/// </summary>
private readonly ConcurrentDictionary<ulong, TaskCompletionSource<VoiceServerUpdateEventArgs>> _voiceServerUpdates;
/// <summary>
/// Initializes a new instance of the <see cref="LavalinkNodeConnection"/> class.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="extension">the event.tension.</param>
/// <param name="config">The config.</param>
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<ulong, LavalinkGuildConnection>(this.ConnectedGuildsInternal);
this.Statistics = new LavalinkStatistics();
this._lavalinkSocketError = new AsyncEvent<LavalinkNodeConnection, SocketErrorEventArgs>("LAVALINK_SOCKET_ERROR", TimeSpan.Zero, this.Discord.EventErrorHandler);
this._disconnected = new AsyncEvent<LavalinkNodeConnection, NodeDisconnectedEventArgs>("LAVALINK_NODE_DISCONNECTED", TimeSpan.Zero, this.Discord.EventErrorHandler);
this._statsReceived = new AsyncEvent<LavalinkNodeConnection, StatisticsReceivedEventArgs>("LAVALINK_STATS_RECEIVED", TimeSpan.Zero, this.Discord.EventErrorHandler);
this._playerUpdated = new AsyncEvent<LavalinkGuildConnection, PlayerUpdateEventArgs>("LAVALINK_PLAYER_UPDATED", TimeSpan.Zero, this.Discord.EventErrorHandler);
this._playbackStarted = new AsyncEvent<LavalinkGuildConnection, TrackStartEventArgs>("LAVALINK_PLAYBACK_STARTED", TimeSpan.Zero, this.Discord.EventErrorHandler);
this._playbackFinished = new AsyncEvent<LavalinkGuildConnection, TrackFinishEventArgs>("LAVALINK_PLAYBACK_FINISHED", TimeSpan.Zero, this.Discord.EventErrorHandler);
this._trackStuck = new AsyncEvent<LavalinkGuildConnection, TrackStuckEventArgs>("LAVALINK_TRACK_STUCK", TimeSpan.Zero, this.Discord.EventErrorHandler);
this._trackException = new AsyncEvent<LavalinkGuildConnection, TrackExceptionEventArgs>("LAVALINK_TRACK_EXCEPTION", TimeSpan.Zero, this.Discord.EventErrorHandler);
this._voiceServerUpdates = new ConcurrentDictionary<ulong, TaskCompletionSource<VoiceServerUpdateEventArgs>>();
this._voiceStateUpdates = new ConcurrentDictionary<ulong, TaskCompletionSource<VoiceStateUpdateEventArgs>>();
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);
}
/// <summary>
/// Establishes a connection to the Lavalink node.
/// </summary>
/// <returns></returns>
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));
this._webSocket.AddDefaultHeader("Client-Name", $"DisCatSharp.Lavalink version {this.Discord.VersionString}");
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;
}
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);
}
/// <summary>
/// Stops this Lavalink node connection and frees resources.
/// </summary>
/// <returns></returns>
public async Task StopAsync()
{
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);
}
/// <summary>
/// Connects this Lavalink node to specified Discord channel.
/// </summary>
/// <param name="channel">Voice channel to connect to.</param>
/// <returns>Channel connection, which allows for playback control.</returns>
public async Task<LavalinkGuildConnection> ConnectAsync(DiscordChannel channel)
{
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<VoiceStateUpdateEventArgs>();
var vsrut = new TaskCompletionSource<VoiceServerUpdateEventArgs>();
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.ConnectedGuildsInternal[channel.Guild.Id] = con;
return con;
}
/// <summary>
/// Gets a Lavalink connection to specified Discord channel.
/// </summary>
/// <param name="guild">Guild to get connection for.</param>
/// <returns>Channel connection, which allows for playback control.</returns>
public LavalinkGuildConnection GetGuildConnection(DiscordGuild guild)
=> this.ConnectedGuildsInternal.TryGetValue(guild.Id, out var lgc) && lgc.IsConnected ? lgc : null;
/// <summary>
/// Sends the payload async.
/// </summary>
/// <param name="payload">The payload.</param>
internal async Task SendPayloadAsync(LavalinkPayload payload)
=> await this.WsSendAsync(JsonConvert.SerializeObject(payload, Formatting.None)).ConfigureAwait(false);
/// <summary>
/// Webs the socket_ on message.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">the event.ent.</param>
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<LavalinkState>();
if (this.ConnectedGuildsInternal.TryGetValue(gid, out var lvl))
await lvl.InternalUpdatePlayerStateAsync(state).ConfigureAwait(false);
break;
case "stats":
var statsRaw = jsonData.ToObject<LavalinkStats>();
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<EventType>();
var guildId = (ulong)jsonData["guildId"];
switch (evtype)
{
case EventType.TrackStartEvent:
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.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.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:
var severity = LoadFailedSeverity.Common;
switch (jsonData["severity"].ToString())
{
case "COMMON":
severity = LoadFailedSeverity.Common;
break;
case "SUSPICIOUS":
severity = LoadFailedSeverity.Suspicious;
break;
case "FAULT":
severity = LoadFailedSeverity.Fault;
break;
}
if (this.ConnectedGuildsInternal.TryGetValue(guildId, out var lvlEvte))
await lvlEvte.InternalTrackExceptionAsync(new LavalinkLoadFailedInfo { Message = jsonData["message"].ToString(), Severity = severity }, jsonData["track"].ToString()).ConfigureAwait(false);
break;
case EventType.WebSocketClosedEvent:
if (this.ConnectedGuildsInternal.TryGetValue(guildId, out var lvlEwsce))
{
lvlEwsce.VoiceWsDisconnectTcs.SetResult(true);
await lvlEwsce.InternalWebSocketClosedAsync(new WebSocketCloseEventArgs(jsonData["code"].ToObject<int>(), jsonData["reason"].ToString(), jsonData["byRemote"].ToObject<bool>(), this.Discord.ServiceProvider)).ConfigureAwait(false);
}
break;
}
break;
}
}
/// <summary>
/// Webs the socket_ on exception.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">the event.</param>
private Task WebSocket_OnException(IWebSocketClient client, SocketErrorEventArgs e)
=> this._lavalinkSocketError.InvokeAsync(this, new SocketErrorEventArgs(client.ServiceProvider) { Exception = e.Exception });
/// <summary>
/// Webs the socket_ on disconnect.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">the event.</param>
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.ConnectedGuildsInternal)
{
await kvp.Value.SendVoiceUpdateAsync().ConfigureAwait(false);
_ = 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);
}
}
/// <summary>
/// Webs the socket_ on connect.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="ea">the event..</param>
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);
}
/// <summary>
/// Con_S the channel disconnected.
/// </summary>
/// <param name="con">The con.</param>
private void Con_ChannelDisconnected(LavalinkGuildConnection con)
=> this.ConnectedGuildsInternal.TryRemove(con.GuildId, out _);
/// <summary>
/// Discord voice state updated.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">the event.</param>
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.ConnectedGuildsInternal.TryGetValue(e.Guild.Id, out var lvlgc))
lvlgc.VoiceStateUpdate = e;
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.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;
}
/// <summary>
/// Discord voice server updated.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">the event.</param>
private Task Discord_VoiceServerUpdated(DiscordClient client, VoiceServerUpdateEventArgs e)
{
var gld = e.Guild;
if (gld == null)
return Task.CompletedTask;
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;
}
/// <summary>
/// Ws the send async.
/// </summary>
/// <param name="payload">The payload.</param>
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/LavalinkRestClient.cs b/DisCatSharp.Lavalink/LavalinkRestClient.cs
index 926d58ea4..c9f94c1f2 100644
--- a/DisCatSharp.Lavalink/LavalinkRestClient.cs
+++ b/DisCatSharp.Lavalink/LavalinkRestClient.cs
@@ -1,426 +1,426 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net;
using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks;
using DisCatSharp.Lavalink.Entities;
using DisCatSharp.Net;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace DisCatSharp.Lavalink;
/// <summary>
/// Represents a class for Lavalink REST calls.
/// </summary>
public sealed class LavalinkRestClient
{
/// <summary>
/// Gets the REST connection endpoint for this client.
/// </summary>
public ConnectionEndpoint RestEndpoint { get; private set; }
private HttpClient _http;
private readonly ILogger _logger;
private readonly Lazy<string> _dcsVersionString = new(() =>
{
var a = typeof(DiscordClient).GetTypeInfo().Assembly;
var iv = a.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
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;
});
/// <summary>
/// Creates a new Lavalink REST client.
/// </summary>
/// <param name="restEndpoint">The REST server endpoint to connect to.</param>
/// <param name="password">The password for the remote server.</param>
public LavalinkRestClient(ConnectionEndpoint restEndpoint, string password)
{
this.RestEndpoint = restEndpoint;
this.ConfigureHttpHandling(password);
}
/// <summary>
/// Initializes a new instance of the <see cref="LavalinkRestClient"/> class.
/// </summary>
/// <param name="config">The config.</param>
/// <param name="client">The client.</param>
internal LavalinkRestClient(LavalinkConfiguration config, BaseDiscordClient client)
{
this.RestEndpoint = config.RestEndpoint;
this._logger = client.Logger;
this.ConfigureHttpHandling(config.Password, client);
}
/// <summary>
/// Gets the version of the Lavalink server.
/// </summary>
/// <returns></returns>
public Task<string> GetVersionAsync()
{
var versionUri = new Uri($"{this.RestEndpoint.ToHttpString()}{Endpoints.VERSION}");
return this.InternalGetVersionAsync(versionUri);
}
#region Track_Loading
/// <summary>
/// Searches for specified terms.
/// </summary>
/// <param name="searchQuery">What to search for.</param>
/// <param name="type">What platform will search for.</param>
/// <returns>A collection of tracks matching the criteria.</returns>
public Task<LavalinkLoadResult> GetTracksAsync(string searchQuery, LavalinkSearchType type = LavalinkSearchType.Youtube)
{
var prefix = type switch
{
LavalinkSearchType.Youtube => "ytsearch:",
LavalinkSearchType.SoundCloud => "scsearch:",
LavalinkSearchType.Plain => "",
LavalinkSearchType.Spotify => "spsearch:",
LavalinkSearchType.AppleMusic => "amsearch:",
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
};
var str = WebUtility.UrlEncode(prefix + searchQuery);
var tracksUri = new Uri($"{this.RestEndpoint.ToHttpString()}{Endpoints.LOAD_TRACKS}?identifier={str}");
return this.InternalResolveTracksAsync(tracksUri);
}
/// <summary>
/// Loads tracks from specified URL.
/// </summary>
/// <param name="uri">URL to load tracks from.</param>
/// <returns>A collection of tracks from the URL.</returns>
public Task<LavalinkLoadResult> GetTracksAsync(Uri uri)
{
var str = WebUtility.UrlEncode(uri.ToString());
var tracksUri = new Uri($"{this.RestEndpoint.ToHttpString()}{Endpoints.LOAD_TRACKS}?identifier={str}");
return this.InternalResolveTracksAsync(tracksUri);
}
/// <summary>
/// Loads tracks from a local file.
/// </summary>
/// <param name="file">File to load tracks from.</param>
/// <returns>A collection of tracks from the file.</returns>
public Task<LavalinkLoadResult> GetTracksAsync(FileInfo file)
{
var str = WebUtility.UrlEncode(file.FullName);
var tracksUri = new Uri($"{this.RestEndpoint.ToHttpString()}{Endpoints.LOAD_TRACKS}?identifier={str}");
return this.InternalResolveTracksAsync(tracksUri);
}
/// <summary>
/// Decodes a base64 track string into a Lavalink track object.
/// </summary>
/// <param name="trackString">The base64 track string.</param>
/// <returns></returns>
public Task<LavalinkTrack> DecodeTrackAsync(string trackString)
{
var str = WebUtility.UrlEncode(trackString);
var decodeTrackUri = new Uri($"{this.RestEndpoint.ToHttpString()}{Endpoints.DECODE_TRACK}?track={str}");
return this.InternalDecodeTrackAsync(decodeTrackUri);
}
/// <summary>
/// Decodes an array of base64 track strings into Lavalink track objects.
/// </summary>
/// <param name="trackStrings">The array of base64 track strings.</param>
/// <returns></returns>
public Task<List<LavalinkTrack>> DecodeTracksAsync(string[] trackStrings)
{
var decodeTracksUri = new Uri($"{this.RestEndpoint.ToHttpString()}{Endpoints.DECODE_TRACKS}");
return this.InternalDecodeTracksAsync(decodeTracksUri, trackStrings);
}
/// <summary>
/// Decodes a list of base64 track strings into Lavalink track objects.
/// </summary>
/// <param name="trackStrings">The list of base64 track strings.</param>
/// <returns></returns>
public Task<List<LavalinkTrack>> DecodeTracksAsync(List<string> trackStrings)
{
var decodeTracksUri = new Uri($"{this.RestEndpoint.ToHttpString()}{Endpoints.DECODE_TRACKS}");
return this.InternalDecodeTracksAsync(decodeTracksUri, trackStrings.ToArray());
}
#endregion
#region Route_Planner
/// <summary>
/// Retrieves statistics from the route planner.
/// </summary>
/// <returns>The status (<see cref="DisCatSharp.Lavalink.Entities.LavalinkRouteStatus"/>) details.</returns>
public Task<LavalinkRouteStatus> GetRoutePlannerStatusAsync()
{
var routeStatusUri = new Uri($"{this.RestEndpoint.ToHttpString()}{Endpoints.ROUTE_PLANNER}{Endpoints.STATUS}");
return this.InternalGetRoutePlannerStatusAsync(routeStatusUri);
}
/// <summary>
/// Unmarks a failed route planner IP Address.
/// </summary>
/// <param name="address">The IP address name to unmark.</param>
/// <returns></returns>
public Task FreeAddressAsync(string address)
{
var routeFreeAddressUri = new Uri($"{this.RestEndpoint.ToHttpString()}{Endpoints.ROUTE_PLANNER}{Endpoints.FREE_ADDRESS}");
return this.InternalFreeAddressAsync(routeFreeAddressUri, address);
}
/// <summary>
/// Unmarks all failed route planner IP Addresses.
/// </summary>
/// <returns></returns>
public Task FreeAllAddressesAsync()
{
var routeFreeAllAddressesUri = new Uri($"{this.RestEndpoint.ToHttpString()}{Endpoints.ROUTE_PLANNER}{Endpoints.FREE_ALL}");
return this.InternalFreeAllAddressesAsync(routeFreeAllAddressesUri);
}
#endregion
/// <summary>
/// get version async.
/// </summary>
/// <param name="uri">The uri.</param>
/// <returns>A Task.</returns>
internal async Task<string> InternalGetVersionAsync(Uri uri)
{
using var req = await this._http.GetAsync(uri).ConfigureAwait(false);
using var res = await req.Content.ReadAsStreamAsync().ConfigureAwait(false);
using var sr = new StreamReader(res, Utilities.UTF8);
var json = await sr.ReadToEndAsync().ConfigureAwait(false);
return json;
}
#region Internal_Track_Loading
/// <summary>
/// resolve tracks async.
/// </summary>
/// <param name="uri">The uri.</param>
/// <returns>A Task.</returns>
internal async Task<LavalinkLoadResult> InternalResolveTracksAsync(Uri uri)
{
// this function returns a Lavalink 3-like dataset regardless of input data version
var json = "[]";
using (var req = await this._http.GetAsync(uri).ConfigureAwait(false))
using (var res = await req.Content.ReadAsStreamAsync().ConfigureAwait(false))
using (var sr = new StreamReader(res, Utilities.UTF8))
json = await sr.ReadToEndAsync().ConfigureAwait(false);
var jdata = JToken.Parse(json);
if (jdata is JArray jarr)
{
// Lavalink 2.x
var tracks = new List<LavalinkTrack>(jarr.Count);
foreach (var jt in jarr)
{
var track = jt["info"].ToObject<LavalinkTrack>();
track.TrackString = jt["track"].ToString();
tracks.Add(track);
}
return new LavalinkLoadResult
{
PlaylistInfo = default,
LoadResultType = tracks.Count == 0 ? LavalinkLoadResultType.LoadFailed : LavalinkLoadResultType.TrackLoaded,
Tracks = tracks
};
}
else if (jdata is JObject jo)
{
// Lavalink 3.x
jarr = jo["tracks"] as JArray;
var loadInfo = jo.ToObject<LavalinkLoadResult>();
var tracks = new List<LavalinkTrack>(jarr.Count);
foreach (var jt in jarr)
{
var track = jt["info"].ToObject<LavalinkTrack>();
track.TrackString = jt["track"].ToString();
tracks.Add(track);
}
loadInfo.Tracks = new(tracks);
return loadInfo;
}
else
return null;
}
/// <summary>
/// decode track async.
/// </summary>
/// <param name="uri">The uri.</param>
/// <returns>A Task.</returns>
internal async Task<LavalinkTrack> InternalDecodeTrackAsync(Uri uri)
{
using var req = await this._http.GetAsync(uri).ConfigureAwait(false);
using var res = await req.Content.ReadAsStreamAsync().ConfigureAwait(false);
using var sr = new StreamReader(res, Utilities.UTF8);
var json = await sr.ReadToEndAsync().ConfigureAwait(false);
if (!req.IsSuccessStatusCode)
{
var jsonError = JObject.Parse(json);
this._logger?.LogError(LavalinkEvents.LavalinkDecodeError, "Unable to decode track strings: {0}", jsonError["message"]);
return null;
}
var track = JsonConvert.DeserializeObject<LavalinkTrack>(json);
return track;
}
/// <summary>
/// decode tracks async.
/// </summary>
/// <param name="uri">The uri.</param>
/// <param name="ids">The ids.</param>
/// <returns>A Task.</returns>
internal async Task<List<LavalinkTrack>> InternalDecodeTracksAsync(Uri uri, string[] ids)
{
var jsonOut = JsonConvert.SerializeObject(ids);
var content = new StringContent(jsonOut, Utilities.UTF8, "application/json");
using var req = await this._http.PostAsync(uri, content).ConfigureAwait(false);
using var res = await req.Content.ReadAsStreamAsync().ConfigureAwait(false);
using var sr = new StreamReader(res, Utilities.UTF8);
var jsonIn = await sr.ReadToEndAsync().ConfigureAwait(false);
if (!req.IsSuccessStatusCode)
{
var jsonError = JObject.Parse(jsonIn);
this._logger?.LogError(LavalinkEvents.LavalinkDecodeError, "Unable to decode track strings", jsonError["message"]);
return null;
}
var jarr = JToken.Parse(jsonIn) as JArray;
var decodedTracks = new LavalinkTrack[jarr.Count];
for (var i = 0; i < decodedTracks.Length; i++)
{
decodedTracks[i] = JsonConvert.DeserializeObject<LavalinkTrack>(jarr[i]["info"].ToString());
decodedTracks[i].TrackString = jarr[i]["track"].ToString();
}
return decodedTracks.ToList();
}
#endregion
#region Internal_Route_Planner
/// <summary>
/// get route planner status async.
/// </summary>
/// <param name="uri">The uri.</param>
/// <returns>A Task.</returns>
internal async Task<LavalinkRouteStatus> InternalGetRoutePlannerStatusAsync(Uri uri)
{
using var req = await this._http.GetAsync(uri).ConfigureAwait(false);
using var res = await req.Content.ReadAsStreamAsync().ConfigureAwait(false);
using var sr = new StreamReader(res, Utilities.UTF8);
var json = await sr.ReadToEndAsync().ConfigureAwait(false);
var status = JsonConvert.DeserializeObject<LavalinkRouteStatus>(json);
return status;
}
/// <summary>
/// free address async.
/// </summary>
/// <param name="uri">The uri.</param>
/// <param name="address">The address.</param>
/// <returns>A Task.</returns>
internal async Task InternalFreeAddressAsync(Uri uri, string address)
{
var payload = new StringContent(address, Utilities.UTF8, "application/json");
using var req = await this._http.PostAsync(uri, payload).ConfigureAwait(false);
if (req.StatusCode == HttpStatusCode.InternalServerError)
this._logger?.LogWarning(LavalinkEvents.LavalinkRestError, "Request to {0} returned an internal server error - your server route planner configuration is likely incorrect", uri);
}
/// <summary>
/// free all addresses async.
/// </summary>
/// <param name="uri">The uri.</param>
/// <returns>A Task.</returns>
internal async Task InternalFreeAllAddressesAsync(Uri uri)
{
var httpReq = new HttpRequestMessage(HttpMethod.Post, uri);
using var req = await this._http.SendAsync(httpReq).ConfigureAwait(false);
if (req.StatusCode == HttpStatusCode.InternalServerError)
this._logger?.LogWarning(LavalinkEvents.LavalinkRestError, "Request to {0} returned an internal server error - your server route planner configuration is likely incorrect", uri);
}
#endregion
/// <summary>
/// Configures the http handling.
/// </summary>
/// <param name="password">The password.</param>
/// <param name="client">The client.</param>
private void ConfigureHttpHandling(string password, BaseDiscordClient client = null)
{
var httphandler = new HttpClientHandler
{
UseCookies = false,
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip,
UseProxy = client != null && client.Configuration.Proxy != null
};
if (httphandler.UseProxy) // because mono doesn't implement this properly
httphandler.Proxy = client.Configuration.Proxy;
this._http = new HttpClient(httphandler);
this._http.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", $"DisCatSharp.LavaLink/{this._dcsVersionString}");
this._http.DefaultRequestHeaders.TryAddWithoutValidation("Client-Name", $"DisCatSharp");
this._http.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", password);
}
}
diff --git a/DisCatSharp.Lavalink/LavalinkRestEndpoints.cs b/DisCatSharp.Lavalink/LavalinkRestEndpoints.cs
index 0a469be58..1d372b3d4 100644
--- a/DisCatSharp.Lavalink/LavalinkRestEndpoints.cs
+++ b/DisCatSharp.Lavalink/LavalinkRestEndpoints.cs
@@ -1,66 +1,66 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Lavalink;
/// <summary>
/// Represents the lavalink endpoints.
/// </summary>
internal static class Endpoints
{
/// <summary>
/// The version endpoint.
/// </summary>
internal const string VERSION = "/version";
//Track loading
/// <summary>
/// The load tracks endpoint.
/// </summary>
internal const string LOAD_TRACKS = "/loadtracks";
/// <summary>
/// The decode track endpoint.
/// </summary>
internal const string DECODE_TRACK = "/decodetrack";
/// <summary>
/// The decode tracks endpoint.
/// </summary>
internal const string DECODE_TRACKS = "/decodetracks";
//Route Planner
/// <summary>
/// The route planner endpoint.
/// </summary>
internal const string ROUTE_PLANNER = "/routeplanner";
/// <summary>
/// The status endpoint.
/// </summary>
internal const string STATUS = "/status";
/// <summary>
/// The free address endpoint.
/// </summary>
internal const string FREE_ADDRESS = "/free/address";
/// <summary>
/// The free all endpoint.
/// </summary>
internal const string FREE_ALL = "/free/all";
}
diff --git a/DisCatSharp.Lavalink/LavalinkTrack.cs b/DisCatSharp.Lavalink/LavalinkTrack.cs
index 1a3ea4f58..9ffdc0950 100644
--- a/DisCatSharp.Lavalink/LavalinkTrack.cs
+++ b/DisCatSharp.Lavalink/LavalinkTrack.cs
@@ -1,231 +1,231 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a lavalink track.
/// </summary>
public class LavalinkTrack
{
/// <summary>
/// Gets or sets the ID of the track to play.
/// </summary>
[JsonIgnore]
public string TrackString { get; set; }
/// <summary>
/// Gets the identifier of the track.
/// </summary>
[JsonProperty("identifier")]
public string Identifier { get; internal set; }
/// <summary>
/// Gets whether the track is seekable.
/// </summary>
[JsonProperty("isSeekable")]
public bool IsSeekable { get; internal set; }
/// <summary>
/// Gets the author of the track.
/// </summary>
[JsonProperty("author")]
public string Author { get; internal set; }
/// <summary>
/// Gets the track's duration.
/// </summary>
[JsonIgnore]
public TimeSpan Length => !this.IsStream ? TimeSpan.FromMilliseconds(this.LengthInternal) : TimeSpan.Zero;
[JsonProperty("length")]
internal long LengthInternal;
/// <summary>
/// Gets whether the track is a stream.
/// </summary>
[JsonProperty("isStream")]
public bool IsStream { get; internal set; }
/// <summary>
/// Gets the starting position of the track.
/// </summary>
[JsonIgnore]
public TimeSpan Position => TimeSpan.FromMilliseconds(this.PositionInternal);
[JsonProperty("position")]
internal long PositionInternal;
/// <summary>
/// Gets the title of the track.
/// </summary>
[JsonProperty("title")]
public string Title { get; internal set; }
/// <summary>
/// Gets the source Uri of this track.
/// </summary>
[JsonProperty("uri")]
public Uri Uri { get; internal set; }
/// <summary>
/// Gets the source name that this track was retrieved from.
/// </summary>
[JsonProperty("sourceName")]
public string SourceName { get; internal set; }
}
/// <summary>
/// Represents Lavalink track loading results.
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum LavalinkLoadResultType
{
/// <summary>
/// Specifies that track was loaded successfully.
/// </summary>
[EnumMember(Value = "TRACK_LOADED")]
TrackLoaded,
/// <summary>
/// Specifies that playlist was loaded successfully.
/// </summary>
[EnumMember(Value = "PLAYLIST_LOADED")]
PlaylistLoaded,
/// <summary>
/// Specifies that the result set contains search results.
/// </summary>
[EnumMember(Value = "SEARCH_RESULT")]
SearchResult,
/// <summary>
/// Specifies that the search yielded no results.
/// </summary>
[EnumMember(Value = "NO_MATCHES")]
NoMatches,
/// <summary>
/// Specifies that the track failed to load.
/// </summary>
[EnumMember(Value = "LOAD_FAILED")]
LoadFailed
}
/// <summary>
/// Represents information about playlist that was loaded by Lavalink.
/// </summary>
public struct LavalinkPlaylistInfo
{
/// <summary>
/// Gets the name of the playlist being loaded.
/// </summary>
[JsonProperty("name")]
public string Name { get; internal set; }
/// <summary>
/// Gets the index of the track that was selected in this playlist.
/// </summary>
[JsonProperty("selectedTrack")]
public int SelectedTrack { get; internal set; }
}
/// <summary>
/// Represents information about track loading request.
/// </summary>
public class LavalinkLoadResult
{
/// <summary>
/// Gets the loading result type for this request.
/// </summary>
[JsonProperty("loadType")]
public LavalinkLoadResultType LoadResultType { get; internal set; }
/// <summary>
/// <para>Gets the information about the playlist loaded as a result of this request.</para>
/// <para>Only applicable if <see cref="LoadResultType"/> is set to <see cref="LavalinkLoadResultType.PlaylistLoaded"/>.</para>
/// </summary>
[JsonProperty("playlistInfo")]
public LavalinkPlaylistInfo PlaylistInfo { get; internal set; }
/// <summary>
/// Gets the exception details if a track loading failed.
/// </summary>
[JsonProperty("exception", NullValueHandling = NullValueHandling.Ignore)]
public LavalinkLoadFailedInfo Exception { get; internal set; }
/// <summary>
/// Gets the tracks that were loaded as a result of this request.
/// </summary>
//[JsonProperty("tracks")]
[JsonIgnore]
public List<LavalinkTrack> Tracks { get; internal set; }
}
/// <summary>
/// Represents properties sent when a Lavalink track is unable to load.
/// </summary>
public struct LavalinkLoadFailedInfo
{
/// <summary>
/// Gets the message of the sent exception.
/// </summary>
[JsonProperty("message")]
public string Message { get; internal set; }
/// <summary>
/// Gets the severity level of the track loading failure.
/// </summary>
[JsonProperty("severity")]
public LoadFailedSeverity Severity { get; internal set; }
}
/// <summary>
/// Represents severity level of the track loading failure.
/// </summary>
public enum LoadFailedSeverity
{
/// <summary>
/// Indicates a known cause for the failure, and not because of Lavaplayer.
/// </summary>
[EnumMember(Value = "COMMON")]
Common,
/// <summary>
/// Indicates an unknown cause for the failure, most likely caused by outside sources.
/// </summary>
[EnumMember(Value = "SUSPICIOUS")]
Suspicious,
/// <summary>
/// Indicates an issue with Lavaplayer or otherwise no other way to determine the cause.
/// </summary>
[EnumMember(Value = "FAULT")]
Fault
}
diff --git a/DisCatSharp.Lavalink/LavalinkUtil.cs b/DisCatSharp.Lavalink/LavalinkUtil.cs
index 479c74ddf..7e802a9c5 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-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Various utilities for Lavalink.
/// </summary>
public static class LavalinkUtilities
{
/// <summary>
/// Indicates whether a new track should be started after receiving 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).
/// </summary>
public static bool MayStartNext(this TrackEndReason reason)
=> reason == TrackEndReason.Finished || reason == TrackEndReason.LoadFailed;
/// <summary>
/// Decodes a Lavalink track string.
/// </summary>
/// <param name="track">Track string to decode.</param>
/// <returns>Decoded Lavalink track.</returns>
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.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;
}
}
/// <inheritdoc />
/// <summary>
/// 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.
/// </summary>
internal class JavaBinaryReader : BinaryReader
{
private static readonly Encoding s_utf8NoBom = new UTF8Encoding();
/// <summary>
/// Initializes a new instance of the <see cref="JavaBinaryReader"/> class.
/// </summary>
/// <param name="ms">The ms.</param>
public JavaBinaryReader(Stream ms) : base(ms, s_utf8NoBom)
{
}
// https://docs.oracle.com/javase/7/docs/api/java/io/DataInput.html#readUTF()
/// <summary>
/// Reads the java utf8.
/// </summary>
/// <returns>A string.</returns>
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
/// <summary>
/// Reads the nullable string.
/// </summary>
/// <returns>A string.</returns>
public string ReadNullableString() => this.ReadBoolean() ? this.ReadJavaUtf8() : null;
// swap endianness
/// <summary>
/// Reads the decimal.
/// </summary>
/// <returns>A decimal.</returns>
public override decimal ReadDecimal() => throw new MissingMethodException("This method does not have a Java equivalent");
// from https://github.com/Zoltu/Zoltu.EndianAwareBinaryReaderWriter under CC0
/// <summary>
/// Reads the single.
/// </summary>
/// <returns>A float.</returns>
public override float ReadSingle() => this.Read(4, BitConverter.ToSingle);
/// <summary>
/// Reads the double.
/// </summary>
/// <returns>A double.</returns>
public override double ReadDouble() => this.Read(8, BitConverter.ToDouble);
/// <summary>
/// Reads the int16.
/// </summary>
/// <returns>A short.</returns>
public override short ReadInt16() => this.Read(2, BitConverter.ToInt16);
/// <summary>
/// Reads the int32.
/// </summary>
/// <returns>An int.</returns>
public override int ReadInt32() => this.Read(4, BitConverter.ToInt32);
/// <summary>
/// Reads the int64.
/// </summary>
/// <returns>A long.</returns>
public override long ReadInt64() => this.Read(8, BitConverter.ToInt64);
/// <summary>
/// Reads the u int16.
/// </summary>
/// <returns>An ushort.</returns>
public override ushort ReadUInt16() => this.Read(2, BitConverter.ToUInt16);
/// <summary>
/// Reads the u int32.
/// </summary>
/// <returns>An uint.</returns>
public override uint ReadUInt32() => this.Read(4, BitConverter.ToUInt32);
/// <summary>
/// Reads the u int64.
/// </summary>
/// <returns>An ulong.</returns>
public override ulong ReadUInt64() => this.Read(8, BitConverter.ToUInt64);
/// <summary>
/// Reads the.
/// </summary>
/// <param name="size">The size.</param>
/// <param name="converter">The converter.</param>
/// <returns>A T.</returns>
private T Read<T>(int size, Func<byte[], int, T> converter) where T : struct
{
//Contract.Requires(size >= 0);
//Contract.Requires(converter != null);
var bytes = this.GetNextBytesNativeEndian(size);
return converter(bytes, 0);
}
/// <summary>
/// Gets the next bytes native endian.
/// </summary>
/// <param name="count">The count.</param>
/// <returns>An array of byte.</returns>
private byte[] GetNextBytesNativeEndian(int count)
{
//Contract.Requires(count >= 0);
//Contract.Ensures(Contract.Result<Byte[]>() != null);
//Contract.Ensures(Contract.Result<Byte[]>().Length == count);
var bytes = this.GetNextBytes(count);
if (BitConverter.IsLittleEndian)
Array.Reverse(bytes);
return bytes;
}
/// <summary>
/// Gets the next bytes.
/// </summary>
/// <param name="count">The count.</param>
/// <returns>An array of byte.</returns>
private byte[] GetNextBytes(int count)
{
//Contract.Requires(count >= 0);
//Contract.Ensures(Contract.Result<Byte[]>() != null);
//Contract.Ensures(Contract.Result<Byte[]>().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.Lavalink/Properties/AssemblyProperties.cs b/DisCatSharp.Lavalink/Properties/AssemblyProperties.cs
index 155f91856..520b9efc7 100644
--- a/DisCatSharp.Lavalink/Properties/AssemblyProperties.cs
+++ b/DisCatSharp.Lavalink/Properties/AssemblyProperties.cs
@@ -1,46 +1,46 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("DisCatSharp.ApplicationCommands")]
[assembly: InternalsVisibleTo("DisCatSharp.CommandsNext")]
[assembly: InternalsVisibleTo("DisCatSharp.Common")]
[assembly: InternalsVisibleTo("DisCatSharp.Configuration")]
[assembly: InternalsVisibleTo("DisCatSharp.Experimental")]
[assembly: InternalsVisibleTo("DisCatSharp.Configuration.Tests")]
[assembly: InternalsVisibleTo("DisCatSharp.Hosting")]
[assembly: InternalsVisibleTo("DisCatSharp.Hosting.DependencyInjection")]
[assembly: InternalsVisibleTo("DisCatSharp.Hosting.Tests")]
[assembly: InternalsVisibleTo("DisCatSharp.Interactivity")]
[assembly: InternalsVisibleTo("DisCatSharp")]
[assembly: InternalsVisibleTo("DisCatSharp.Phabricator")]
[assembly: InternalsVisibleTo("DisCatSharp.Support")]
[assembly: InternalsVisibleTo("DisCatSharp.Test")]
[assembly: InternalsVisibleTo("DisCatSharp.VoiceNext")]
[assembly: InternalsVisibleTo("DisCatSharp.VoiceNext.Natives")]
[assembly: InternalsVisibleTo("Nyaw")]
[assembly: InternalsVisibleTo("DisCatSharp.DevTools")]
[assembly: InternalsVisibleTo("DisCatSharp.DocsGenerator")]
[assembly: InternalsVisibleTo("DisCatSharp.StaffApps")]
[assembly: InternalsVisibleTo("Microsoft.DocAsCode")]
[assembly: InternalsVisibleTo("Microsoft.DocAsCode.Metadata.ManagedReference")]
diff --git a/DisCatSharp.VoiceNext/AudioFormat.cs b/DisCatSharp.VoiceNext/AudioFormat.cs
index 2b037f789..8eeeec303 100644
--- a/DisCatSharp.VoiceNext/AudioFormat.cs
+++ b/DisCatSharp.VoiceNext/AudioFormat.cs
@@ -1,163 +1,163 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Runtime.CompilerServices;
namespace DisCatSharp.VoiceNext;
/// <summary>
/// Defines the format of PCM data consumed or produced by Opus.
/// </summary>
public readonly struct AudioFormat
{
/// <summary>
/// Gets the collection of sampling rates (in Hz) the Opus encoder can use.
/// </summary>
public static IReadOnlyCollection<int> AllowedSampleRates { get; } = new ReadOnlyCollection<int>(new[] { 8000, 12000, 16000, 24000, 48000 });
/// <summary>
/// Gets the collection of channel counts the Opus encoder can use.
/// </summary>
public static IReadOnlyCollection<int> AllowedChannelCounts { get; } = new ReadOnlyCollection<int>(new[] { 1, 2 });
/// <summary>
/// Gets the collection of sample durations (in ms) the Opus encoder can use.
/// </summary>
public static IReadOnlyCollection<int> AllowedSampleDurations { get; } = new ReadOnlyCollection<int>(new[] { 5, 10, 20, 40, 60 });
/// <summary>
/// Gets the default audio format. This is a format configured for 48kHz sampling rate, 2 channels, with music quality preset.
/// </summary>
public static AudioFormat Default { get; } = new(48000, 2, VoiceApplication.Music);
/// <summary>
/// Gets the audio sampling rate in Hz.
/// </summary>
public int SampleRate { get; }
/// <summary>
/// Gets the audio channel count.
/// </summary>
public int ChannelCount { get; }
/// <summary>
/// Gets the voice application, which dictates the quality preset.
/// </summary>
public VoiceApplication VoiceApplication { get; }
/// <summary>
/// Creates a new audio format for use with Opus encoder.
/// </summary>
/// <param name="sampleRate">Audio sampling rate in Hz.</param>
/// <param name="channelCount">Number of audio channels in the data.</param>
/// <param name="voiceApplication">Encoder preset to use.</param>
public AudioFormat(int sampleRate = 48000, int channelCount = 2, VoiceApplication voiceApplication = VoiceApplication.Music)
{
if (!AllowedSampleRates.Contains(sampleRate))
throw new ArgumentOutOfRangeException(nameof(sampleRate), "Invalid sample rate specified.");
if (!AllowedChannelCounts.Contains(channelCount))
throw new ArgumentOutOfRangeException(nameof(channelCount), "Invalid channel count specified.");
if (voiceApplication != VoiceApplication.Music && voiceApplication != VoiceApplication.Voice && voiceApplication != VoiceApplication.LowLatency)
throw new ArgumentOutOfRangeException(nameof(voiceApplication), "Invalid voice application specified.");
this.SampleRate = sampleRate;
this.ChannelCount = channelCount;
this.VoiceApplication = voiceApplication;
}
/// <summary>
/// Calculates a sample size in bytes.
/// </summary>
/// <param name="sampleDuration">Millisecond duration of a sample.</param>
/// <returns>Calculated sample size in bytes.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int CalculateSampleSize(int sampleDuration)
{
if (!AllowedSampleDurations.Contains(sampleDuration))
throw new ArgumentOutOfRangeException(nameof(sampleDuration), "Invalid sample duration specified.");
// Sample size in bytes is a product of the following:
// - duration in milliseconds
// - number of channels
// - sample rate in kHz
// - size of data (in this case, sizeof(int16_t))
// which comes down to below:
return sampleDuration * this.ChannelCount * (this.SampleRate / 1000) * 2;
}
/// <summary>
/// Gets the maximum buffer size for decoding. This method should be called when decoding Opus data to PCM, to ensure sufficient buffer size.
/// </summary>
/// <returns>Buffer size required to decode data.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int GetMaximumBufferSize()
=> this.CalculateMaximumFrameSize();
/// <summary>
/// Calculates the sample duration.
/// </summary>
/// <param name="sampleSize">The sample size.</param>
/// <returns>An int.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal int CalculateSampleDuration(int sampleSize)
=> sampleSize / (this.SampleRate / 1000) / this.ChannelCount / 2 /* sizeof(int16_t) */;
/// <summary>
/// Calculates the frame size.
/// </summary>
/// <param name="sampleDuration">The sample duration.</param>
/// <returns>An int.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal int CalculateFrameSize(int sampleDuration)
=> sampleDuration * (this.SampleRate / 1000);
/// <summary>
/// Calculates the maximum frame size.
/// </summary>
/// <returns>An int.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal int CalculateMaximumFrameSize()
=> 120 * (this.SampleRate / 1000);
/// <summary>
/// Samples the count to sample size.
/// </summary>
/// <param name="sampleCount">The sample count.</param>
/// <returns>An int.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal int SampleCountToSampleSize(int sampleCount)
=> sampleCount * this.ChannelCount * 2 /* sizeof(int16_t) */;
/// <summary>
/// Are the valid.
/// </summary>
/// <returns>A bool.</returns>
internal bool IsValid()
=> AllowedSampleRates.Contains(this.SampleRate) && AllowedChannelCounts.Contains(this.ChannelCount) &&
(this.VoiceApplication == VoiceApplication.Music || this.VoiceApplication == VoiceApplication.Voice || this.VoiceApplication == VoiceApplication.LowLatency);
}
diff --git a/DisCatSharp.VoiceNext/Codec/Helpers.cs b/DisCatSharp.VoiceNext/Codec/Helpers.cs
index f8e069571..0175f04f8 100644
--- a/DisCatSharp.VoiceNext/Codec/Helpers.cs
+++ b/DisCatSharp.VoiceNext/Codec/Helpers.cs
@@ -1,53 +1,53 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace DisCatSharp.VoiceNext.Codec;
/// <summary>
/// The helpers.
/// </summary>
internal static class Helpers
{
/// <summary>
/// Fills the buffer with 0.
/// </summary>
/// <param name="buff">The buffer.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ZeroFill(Span<byte> buff)
{
var zero = 0;
var i = 0;
for (; i < buff.Length / 4; i++)
MemoryMarshal.Write(buff, ref zero);
var remainder = buff.Length % 4;
if (remainder == 0)
return;
for (; i < buff.Length; i++)
buff[i] = 0;
}
}
diff --git a/DisCatSharp.VoiceNext/Codec/Interop.cs b/DisCatSharp.VoiceNext/Codec/Interop.cs
index 9d9a732e3..4ba7861d4 100644
--- a/DisCatSharp.VoiceNext/Codec/Interop.cs
+++ b/DisCatSharp.VoiceNext/Codec/Interop.cs
@@ -1,392 +1,392 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Runtime.InteropServices;
namespace DisCatSharp.VoiceNext.Codec;
/// <summary>
/// This is an interop class. It contains wrapper methods for Opus and Sodium.
/// </summary>
internal static class Interop
{
#region Sodium wrapper
/// <summary>
/// The sodium library name.
/// </summary>
private const string SODIUM_LIBRARY_NAME = "libsodium";
/// <summary>
/// Gets the Sodium key size for xsalsa20_poly1305 algorithm.
/// </summary>
public static int SodiumKeySize { get; } = (int)_SodiumSecretBoxKeySize();
/// <summary>
/// Gets the Sodium nonce size for xsalsa20_poly1305 algorithm.
/// </summary>
public static int SodiumNonceSize { get; } = (int)_SodiumSecretBoxNonceSize();
/// <summary>
/// Gets the Sodium MAC size for xsalsa20_poly1305 algorithm.
/// </summary>
public static int SodiumMacSize { get; } = (int)_SodiumSecretBoxMacSize();
/// <summary>
/// _S the sodium secret box key size.
/// </summary>
/// <returns>An UIntPtr.</returns>
[DllImport(SODIUM_LIBRARY_NAME, CallingConvention = CallingConvention.Cdecl, EntryPoint = "crypto_secretbox_xsalsa20poly1305_keybytes")]
[return: MarshalAs(UnmanagedType.SysUInt)]
private static extern UIntPtr _SodiumSecretBoxKeySize();
/// <summary>
/// _S the sodium secret box nonce size.
/// </summary>
/// <returns>An UIntPtr.</returns>
[DllImport(SODIUM_LIBRARY_NAME, CallingConvention = CallingConvention.Cdecl, EntryPoint = "crypto_secretbox_xsalsa20poly1305_noncebytes")]
[return: MarshalAs(UnmanagedType.SysUInt)]
private static extern UIntPtr _SodiumSecretBoxNonceSize();
/// <summary>
/// _S the sodium secret box mac size.
/// </summary>
/// <returns>An UIntPtr.</returns>
[DllImport(SODIUM_LIBRARY_NAME, CallingConvention = CallingConvention.Cdecl, EntryPoint = "crypto_secretbox_xsalsa20poly1305_macbytes")]
[return: MarshalAs(UnmanagedType.SysUInt)]
private static extern UIntPtr _SodiumSecretBoxMacSize();
/// <summary>
/// _S the sodium secret box create.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <param name="message">The message.</param>
/// <param name="messageLength">The message length.</param>
/// <param name="nonce">The nonce.</param>
/// <param name="key">The key.</param>
/// <returns>An int.</returns>
[DllImport(SODIUM_LIBRARY_NAME, CallingConvention = CallingConvention.Cdecl, EntryPoint = "crypto_secretbox_easy")]
private static unsafe extern int _SodiumSecretBoxCreate(byte* buffer, byte* message, ulong messageLength, byte* nonce, byte* key);
/// <summary>
/// _S the sodium secret box open.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <param name="encryptedMessage">The encrypted message.</param>
/// <param name="encryptedLength">The encrypted length.</param>
/// <param name="nonce">The nonce.</param>
/// <param name="key">The key.</param>
/// <returns>An int.</returns>
[DllImport(SODIUM_LIBRARY_NAME, CallingConvention = CallingConvention.Cdecl, EntryPoint = "crypto_secretbox_open_easy")]
private static unsafe extern int _SodiumSecretBoxOpen(byte* buffer, byte* encryptedMessage, ulong encryptedLength, byte* nonce, byte* key);
/// <summary>
/// Encrypts supplied buffer using xsalsa20_poly1305 algorithm, using supplied key and nonce to perform encryption.
/// </summary>
/// <param name="source">Contents to encrypt.</param>
/// <param name="target">Buffer to encrypt to.</param>
/// <param name="key">Key to use for encryption.</param>
/// <param name="nonce">Nonce to use for encryption.</param>
/// <returns>Encryption status.</returns>
public static unsafe int Encrypt(ReadOnlySpan<byte> source, Span<byte> target, ReadOnlySpan<byte> key, ReadOnlySpan<byte> nonce)
{
var status = 0;
fixed (byte* sourcePtr = &source.GetPinnableReference())
fixed (byte* targetPtr = &target.GetPinnableReference())
fixed (byte* keyPtr = &key.GetPinnableReference())
fixed (byte* noncePtr = &nonce.GetPinnableReference())
status = _SodiumSecretBoxCreate(targetPtr, sourcePtr, (ulong)source.Length, noncePtr, keyPtr);
return status;
}
/// <summary>
/// Decrypts supplied buffer using xsalsa20_poly1305 algorithm, using supplied key and nonce to perform decryption.
/// </summary>
/// <param name="source">Buffer to decrypt from.</param>
/// <param name="target">Decrypted message buffer.</param>
/// <param name="key">Key to use for decryption.</param>
/// <param name="nonce">Nonce to use for decryption.</param>
/// <returns>Decryption status.</returns>
public static unsafe int Decrypt(ReadOnlySpan<byte> source, Span<byte> target, ReadOnlySpan<byte> key, ReadOnlySpan<byte> nonce)
{
var status = 0;
fixed (byte* sourcePtr = &source.GetPinnableReference())
fixed (byte* targetPtr = &target.GetPinnableReference())
fixed (byte* keyPtr = &key.GetPinnableReference())
fixed (byte* noncePtr = &nonce.GetPinnableReference())
status = _SodiumSecretBoxOpen(targetPtr, sourcePtr, (ulong)source.Length, noncePtr, keyPtr);
return status;
}
#endregion
#region Opus wrapper
/// <summary>
/// The opus library name.
/// </summary>
private const string OPUS_LIBRARY_NAME = "libopus";
/// <summary>
/// _S the opus create encoder.
/// </summary>
/// <param name="sampleRate">The sample rate.</param>
/// <param name="channels">The channels.</param>
/// <param name="application">The application.</param>
/// <param name="error">The error.</param>
/// <returns>An IntPtr.</returns>
[DllImport(OPUS_LIBRARY_NAME, CallingConvention = CallingConvention.Cdecl, EntryPoint = "opus_encoder_create")]
private static extern IntPtr _OpusCreateEncoder(int sampleRate, int channels, int application, out OpusError error);
/// <summary>
/// Opuses the destroy encoder.
/// </summary>
/// <param name="encoder">The encoder.</param>
[DllImport(OPUS_LIBRARY_NAME, CallingConvention = CallingConvention.Cdecl, EntryPoint = "opus_encoder_destroy")]
public static extern void OpusDestroyEncoder(IntPtr encoder);
/// <summary>
/// _S the opus encode.
/// </summary>
/// <param name="encoder">The encoder.</param>
/// <param name="pcmData">The pcm data.</param>
/// <param name="frameSize">The frame size.</param>
/// <param name="data">The data.</param>
/// <param name="maxDataBytes">The max data bytes.</param>
/// <returns>An int.</returns>
[DllImport(OPUS_LIBRARY_NAME, CallingConvention = CallingConvention.Cdecl, EntryPoint = "opus_encode")]
private static unsafe extern int _OpusEncode(IntPtr encoder, byte* pcmData, int frameSize, byte* data, int maxDataBytes);
/// <summary>
/// _S the opus encoder control.
/// </summary>
/// <param name="encoder">The encoder.</param>
/// <param name="request">The request.</param>
/// <param name="value">The value.</param>
/// <returns>An OpusError.</returns>
[DllImport(OPUS_LIBRARY_NAME, CallingConvention = CallingConvention.Cdecl, EntryPoint = "opus_encoder_ctl")]
private static extern OpusError _OpusEncoderControl(IntPtr encoder, OpusControl request, int value);
/// <summary>
/// _S the opus create decoder.
/// </summary>
/// <param name="sampleRate">The sample rate.</param>
/// <param name="channels">The channels.</param>
/// <param name="error">The error.</param>
/// <returns>An IntPtr.</returns>
[DllImport(OPUS_LIBRARY_NAME, CallingConvention = CallingConvention.Cdecl, EntryPoint = "opus_decoder_create")]
private static extern IntPtr _OpusCreateDecoder(int sampleRate, int channels, out OpusError error);
/// <summary>
/// Opuses the destroy decoder.
/// </summary>
/// <param name="decoder">The decoder.</param>
[DllImport(OPUS_LIBRARY_NAME, CallingConvention = CallingConvention.Cdecl, EntryPoint = "opus_decoder_destroy")]
public static extern void OpusDestroyDecoder(IntPtr decoder);
/// <summary>
/// _S the opus decode.
/// </summary>
/// <param name="decoder">The decoder.</param>
/// <param name="opusData">The opus data.</param>
/// <param name="opusDataLength">The opus data length.</param>
/// <param name="data">The data.</param>
/// <param name="frameSize">The frame size.</param>
/// <param name="decodeFec">The decode fec.</param>
/// <returns>An int.</returns>
[DllImport(OPUS_LIBRARY_NAME, CallingConvention = CallingConvention.Cdecl, EntryPoint = "opus_decode")]
private static unsafe extern int _OpusDecode(IntPtr decoder, byte* opusData, int opusDataLength, byte* data, int frameSize, int decodeFec);
/// <summary>
/// _S the opus get packet channel count.
/// </summary>
/// <param name="opusData">The opus data.</param>
/// <returns>An int.</returns>
[DllImport(OPUS_LIBRARY_NAME, CallingConvention = CallingConvention.Cdecl, EntryPoint = "opus_packet_get_nb_channels")]
private static unsafe extern int _OpusGetPacketChannelCount(byte* opusData);
/// <summary>
/// _S the opus get packet frame count.
/// </summary>
/// <param name="opusData">The opus data.</param>
/// <param name="length">The length.</param>
/// <returns>An int.</returns>
[DllImport(OPUS_LIBRARY_NAME, CallingConvention = CallingConvention.Cdecl, EntryPoint = "opus_packet_get_nb_frames")]
private static unsafe extern int _OpusGetPacketFrameCount(byte* opusData, int length);
/// <summary>
/// _S the opus get packet sample per frame count.
/// </summary>
/// <param name="opusData">The opus data.</param>
/// <param name="samplingRate">The sampling rate.</param>
/// <returns>An int.</returns>
[DllImport(OPUS_LIBRARY_NAME, CallingConvention = CallingConvention.Cdecl, EntryPoint = "opus_packet_get_samples_per_frame")]
private static unsafe extern int _OpusGetPacketSamplePerFrameCount(byte* opusData, int samplingRate);
/// <summary>
/// _S the opus decoder control.
/// </summary>
/// <param name="decoder">The decoder.</param>
/// <param name="request">The request.</param>
/// <param name="value">The value.</param>
/// <returns>An int.</returns>
[DllImport(OPUS_LIBRARY_NAME, CallingConvention = CallingConvention.Cdecl, EntryPoint = "opus_decoder_ctl")]
private static extern int _OpusDecoderControl(IntPtr decoder, OpusControl request, out int value);
/// <summary>
/// Opuses the create encoder.
/// </summary>
/// <param name="audioFormat">The audio format.</param>
/// <returns>An IntPtr.</returns>
public static IntPtr OpusCreateEncoder(AudioFormat audioFormat)
{
var encoder = _OpusCreateEncoder(audioFormat.SampleRate, audioFormat.ChannelCount, (int)audioFormat.VoiceApplication, out var error);
return error != OpusError.Ok ? throw new Exception($"Could not instantiate Opus encoder: {error} ({(int)error}).") : encoder;
}
/// <summary>
/// Opuses the set encoder option.
/// </summary>
/// <param name="encoder">The encoder.</param>
/// <param name="option">The option.</param>
/// <param name="value">The value.</param>
public static void OpusSetEncoderOption(IntPtr encoder, OpusControl option, int value)
{
var error = OpusError.Ok;
if ((error = _OpusEncoderControl(encoder, option, value)) != OpusError.Ok)
throw new Exception($"Could not set Opus encoder option: {error} ({(int)error}).");
}
/// <summary>
/// Opuses the encode.
/// </summary>
/// <param name="encoder">The encoder.</param>
/// <param name="pcm">The pcm.</param>
/// <param name="frameSize">The frame size.</param>
/// <param name="opus">The opus.</param>
public static unsafe void OpusEncode(IntPtr encoder, ReadOnlySpan<byte> pcm, int frameSize, ref Span<byte> opus)
{
var len = 0;
fixed (byte* pcmPtr = &pcm.GetPinnableReference())
fixed (byte* opusPtr = &opus.GetPinnableReference())
len = _OpusEncode(encoder, pcmPtr, frameSize, opusPtr, opus.Length);
if (len < 0)
{
var error = (OpusError)len;
throw new Exception($"Could not encode PCM data to Opus: {error} ({(int)error}).");
}
opus = opus[..len];
}
/// <summary>
/// Opuses the create decoder.
/// </summary>
/// <param name="audioFormat">The audio format.</param>
/// <returns>An IntPtr.</returns>
public static IntPtr OpusCreateDecoder(AudioFormat audioFormat)
{
var decoder = _OpusCreateDecoder(audioFormat.SampleRate, audioFormat.ChannelCount, out var error);
return error != OpusError.Ok ? throw new Exception($"Could not instantiate Opus decoder: {error} ({(int)error}).") : decoder;
}
/// <summary>
/// Opuses the decode.
/// </summary>
/// <param name="decoder">The decoder.</param>
/// <param name="opus">The opus.</param>
/// <param name="frameSize">The frame size.</param>
/// <param name="pcm">The pcm.</param>
/// <param name="useFec">If true, use fec.</param>
/// <returns>An int.</returns>
public static unsafe int OpusDecode(IntPtr decoder, ReadOnlySpan<byte> opus, int frameSize, Span<byte> pcm, bool useFec)
{
var len = 0;
fixed (byte* opusPtr = &opus.GetPinnableReference())
fixed (byte* pcmPtr = &pcm.GetPinnableReference())
len = _OpusDecode(decoder, opusPtr, opus.Length, pcmPtr, frameSize, useFec ? 1 : 0);
if (len < 0)
{
var error = (OpusError)len;
throw new Exception($"Could not decode PCM data from Opus: {error} ({(int)error}).");
}
return len;
}
/// <summary>
/// Opuses the decode.
/// </summary>
/// <param name="decoder">The decoder.</param>
/// <param name="frameSize">The frame size.</param>
/// <param name="pcm">The pcm.</param>
/// <returns>An int.</returns>
public static unsafe int OpusDecode(IntPtr decoder, int frameSize, Span<byte> pcm)
{
var len = 0;
fixed (byte* pcmPtr = &pcm.GetPinnableReference())
len = _OpusDecode(decoder, null, 0, pcmPtr, frameSize, 1);
if (len < 0)
{
var error = (OpusError)len;
throw new Exception($"Could not decode PCM data from Opus: {error} ({(int)error}).");
}
return len;
}
/// <summary>
/// Opuses the get packet metrics.
/// </summary>
/// <param name="opus">The opus.</param>
/// <param name="samplingRate">The sampling rate.</param>
/// <param name="channels">The channels.</param>
/// <param name="frames">The frames.</param>
/// <param name="samplesPerFrame">The samples per frame.</param>
/// <param name="frameSize">The frame size.</param>
public static unsafe void OpusGetPacketMetrics(ReadOnlySpan<byte> opus, int samplingRate, out int channels, out int frames, out int samplesPerFrame, out int frameSize)
{
fixed (byte* opusPtr = &opus.GetPinnableReference())
{
frames = _OpusGetPacketFrameCount(opusPtr, opus.Length);
samplesPerFrame = _OpusGetPacketSamplePerFrameCount(opusPtr, samplingRate);
channels = _OpusGetPacketChannelCount(opusPtr);
}
frameSize = frames * samplesPerFrame;
}
/// <summary>
/// Opuses the get last packet duration.
/// </summary>
/// <param name="decoder">The decoder.</param>
/// <param name="sampleCount">The sample count.</param>
public static void OpusGetLastPacketDuration(IntPtr decoder, out int sampleCount)
=> _OpusDecoderControl(decoder, OpusControl.GetLastPacketDuration, out sampleCount);
#endregion
}
diff --git a/DisCatSharp.VoiceNext/Codec/Opus.cs b/DisCatSharp.VoiceNext/Codec/Opus.cs
index a844520b7..56e326f9a 100644
--- a/DisCatSharp.VoiceNext/Codec/Opus.cs
+++ b/DisCatSharp.VoiceNext/Codec/Opus.cs
@@ -1,288 +1,288 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.VoiceNext.Codec;
/// <summary>
/// The opus.
/// </summary>
internal sealed class Opus : IDisposable
{
/// <summary>
/// Gets the audio format.
/// </summary>
public AudioFormat AudioFormat { get; }
/// <summary>
/// Gets the encoder.
/// </summary>
private readonly IntPtr _encoder;
/// <summary>
/// Gets the managed decoders.
/// </summary>
private readonly List<OpusDecoder> _managedDecoders;
/// <summary>
/// Initializes a new instance of the <see cref="Opus"/> class.
/// </summary>
/// <param name="audioFormat">The audio format.</param>
public Opus(AudioFormat audioFormat)
{
if (!audioFormat.IsValid())
throw new ArgumentException("Invalid audio format specified.", nameof(audioFormat));
this.AudioFormat = audioFormat;
this._encoder = Interop.OpusCreateEncoder(this.AudioFormat);
// Set appropriate encoder options
var sig = OpusSignal.Auto;
switch (this.AudioFormat.VoiceApplication)
{
case VoiceApplication.Music:
sig = OpusSignal.Music;
break;
case VoiceApplication.Voice:
sig = OpusSignal.Voice;
break;
}
Interop.OpusSetEncoderOption(this._encoder, OpusControl.SetSignal, (int)sig);
Interop.OpusSetEncoderOption(this._encoder, OpusControl.SetPacketLossPercent, 15);
Interop.OpusSetEncoderOption(this._encoder, OpusControl.SetInBandFec, 1);
Interop.OpusSetEncoderOption(this._encoder, OpusControl.SetBitrate, 131072);
this._managedDecoders = new List<OpusDecoder>();
}
/// <summary>
/// Encodes the Opus.
/// </summary>
/// <param name="pcm">The pcm.</param>
/// <param name="target">The target.</param>
public void Encode(ReadOnlySpan<byte> pcm, ref Span<byte> target)
{
if (pcm.Length != target.Length)
throw new ArgumentException("PCM and Opus buffer lengths need to be equal.", nameof(target));
var duration = this.AudioFormat.CalculateSampleDuration(pcm.Length);
var frameSize = this.AudioFormat.CalculateFrameSize(duration);
var sampleSize = this.AudioFormat.CalculateSampleSize(duration);
if (pcm.Length != sampleSize)
throw new ArgumentException("Invalid PCM sample size.", nameof(target));
Interop.OpusEncode(this._encoder, pcm, frameSize, ref target);
}
/// <summary>
/// Decodes the Opus.
/// </summary>
/// <param name="decoder">The decoder.</param>
/// <param name="opus">The opus.</param>
/// <param name="target">The target.</param>
/// <param name="useFec">If true, use fec.</param>
/// <param name="outputFormat">The output format.</param>
public void Decode(OpusDecoder decoder, ReadOnlySpan<byte> opus, ref Span<byte> target, bool useFec, out AudioFormat outputFormat)
{
//if (target.Length != this.AudioFormat.CalculateMaximumFrameSize())
// throw new ArgumentException("PCM target buffer size needs to be equal to maximum buffer size for specified audio format.", nameof(target));
Interop.OpusGetPacketMetrics(opus, this.AudioFormat.SampleRate, out var channels, out var frames, out var samplesPerFrame, out var frameSize);
outputFormat = this.AudioFormat.ChannelCount != channels ? new AudioFormat(this.AudioFormat.SampleRate, channels, this.AudioFormat.VoiceApplication) : this.AudioFormat;
if (decoder.AudioFormat.ChannelCount != channels)
decoder.Initialize(outputFormat);
var sampleCount = Interop.OpusDecode(decoder.Decoder, opus, frameSize, target, useFec);
var sampleSize = outputFormat.SampleCountToSampleSize(sampleCount);
target = target[..sampleSize];
}
/// <summary>
/// Processes the packet loss.
/// </summary>
/// <param name="decoder">The decoder.</param>
/// <param name="frameSize">The frame size.</param>
/// <param name="target">The target.</param>
public void ProcessPacketLoss(OpusDecoder decoder, int frameSize, ref Span<byte> target) => Interop.OpusDecode(decoder.Decoder, frameSize, target);
/// <summary>
/// Gets the last packet sample count.
/// </summary>
/// <param name="decoder">The decoder.</param>
/// <returns>An int.</returns>
public int GetLastPacketSampleCount(OpusDecoder decoder)
{
Interop.OpusGetLastPacketDuration(decoder.Decoder, out var sampleCount);
return sampleCount;
}
/// <summary>
/// Creates the decoder.
/// </summary>
/// <returns>An OpusDecoder.</returns>
public OpusDecoder CreateDecoder()
{
lock (this._managedDecoders)
{
var managedDecoder = new OpusDecoder(this);
this._managedDecoders.Add(managedDecoder);
return managedDecoder;
}
}
/// <summary>
/// Destroys the decoder.
/// </summary>
/// <param name="decoder">The decoder.</param>
public void DestroyDecoder(OpusDecoder decoder)
{
lock (this._managedDecoders)
{
if (!this._managedDecoders.Contains(decoder))
return;
this._managedDecoders.Remove(decoder);
decoder.Dispose();
}
}
/// <summary>
/// Disposes the Opus.
/// </summary>
public void Dispose()
{
Interop.OpusDestroyEncoder(this._encoder);
lock (this._managedDecoders)
{
foreach (var decoder in this._managedDecoders)
decoder.Dispose();
}
}
}
/// <summary>
/// Represents an Opus decoder.
/// </summary>
public class OpusDecoder : IDisposable
{
/// <summary>
/// Gets the audio format produced by this decoder.
/// </summary>
public AudioFormat AudioFormat { get; private set; }
/// <summary>
/// Gets the opus.
/// </summary>
internal Opus Opus { get; }
/// <summary>
/// Gets the decoder.
/// </summary>
internal IntPtr Decoder { get; private set; }
private volatile bool _isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="OpusDecoder"/> class.
/// </summary>
/// <param name="managedOpus">The managed opus.</param>
internal OpusDecoder(Opus managedOpus)
{
this.Opus = managedOpus;
}
/// <summary>
/// Used to lazily initialize the decoder to make sure we're
/// using the correct output format, this way we don't end up
/// creating more decoders than we need.
/// </summary>
/// <param name="outputFormat"></param>
internal void Initialize(AudioFormat outputFormat)
{
if (this.Decoder != IntPtr.Zero)
Interop.OpusDestroyDecoder(this.Decoder);
this.AudioFormat = outputFormat;
this.Decoder = Interop.OpusCreateDecoder(outputFormat);
}
/// <summary>
/// Disposes of this Opus decoder.
/// </summary>
public void Dispose()
{
if (this._isDisposed)
return;
this._isDisposed = true;
if (this.Decoder != IntPtr.Zero)
Interop.OpusDestroyDecoder(this.Decoder);
GC.SuppressFinalize(this);
}
}
/// <summary>
/// The opus error.
/// </summary>
[Flags]
internal enum OpusError
{
Ok = 0,
BadArgument = -1,
BufferTooSmall = -2,
InternalError = -3,
InvalidPacket = -4,
Unimplemented = -5,
InvalidState = -6,
AllocationFailure = -7
}
/// <summary>
/// The opus control.
/// </summary>
internal enum OpusControl : int
{
SetBitrate = 4002,
SetBandwidth = 4008,
SetInBandFec = 4012,
SetPacketLossPercent = 4014,
SetSignal = 4024,
ResetState = 4028,
GetLastPacketDuration = 4039
}
/// <summary>
/// The opus signal.
/// </summary>
internal enum OpusSignal : int
{
Auto = -1000,
Voice = 3001,
Music = 3002,
}
diff --git a/DisCatSharp.VoiceNext/Codec/Rtp.cs b/DisCatSharp.VoiceNext/Codec/Rtp.cs
index 1a4653828..b4468b6c2 100644
--- a/DisCatSharp.VoiceNext/Codec/Rtp.cs
+++ b/DisCatSharp.VoiceNext/Codec/Rtp.cs
@@ -1,158 +1,158 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// The rtp.
/// </summary>
internal sealed class Rtp : IDisposable
{
/// <summary>
/// The header size.
/// </summary>
public const int HEADER_SIZE = 12;
/// <summary>
/// The rtp no extension.
/// </summary>
private const byte RTP_NO_EXTENSION = 0x80;
/// <summary>
/// The rtp extension.
/// </summary>
private const byte RTP_EXTENSION = 0x90;
/// <summary>
/// The rtp version.
/// </summary>
private const byte RTP_VERSION = 0x78;
/// <summary>
/// Initializes a new instance of the <see cref="Rtp"/> class.
/// </summary>
public Rtp()
{ }
/// <summary>
/// Encodes the header.
/// </summary>
/// <param name="sequence">The sequence.</param>
/// <param name="timestamp">The timestamp.</param>
/// <param name="ssrc">The ssrc.</param>
/// <param name="target">The target.</param>
public void EncodeHeader(ushort sequence, uint timestamp, uint ssrc, Span<byte> 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)
}
/// <summary>
/// Are the rtp header.
/// </summary>
/// <param name="source">The source.</param>
/// <returns>A bool.</returns>
public bool IsRtpHeader(ReadOnlySpan<byte> source) => source.Length >= HEADER_SIZE && (source[0] == RTP_NO_EXTENSION || source[0] == RTP_EXTENSION) && source[1] == RTP_VERSION;
/// <summary>
/// Decodes the header.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="sequence">The sequence.</param>
/// <param name="timestamp">The timestamp.</param>
/// <param name="ssrc">The ssrc.</param>
/// <param name="hasExtension">If true, has extension.</param>
public void DecodeHeader(ReadOnlySpan<byte> 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..]);
}
/// <summary>
/// Calculates the packet size.
/// </summary>
/// <param name="encryptedLength">The encrypted length.</param>
/// <param name="encryptionMode">The encryption mode.</param>
/// <returns>An int.</returns>
public int CalculatePacketSize(int encryptedLength, EncryptionMode encryptionMode) =>
encryptionMode switch
{
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)),
};
/// <summary>
/// Gets the data from packet.
/// </summary>
/// <param name="packet">The packet.</param>
/// <param name="data">The data.</param>
/// <param name="encryptionMode">The encryption mode.</param>
public void GetDataFromPacket(ReadOnlySpan<byte> packet, out ReadOnlySpan<byte> data, EncryptionMode encryptionMode)
{
switch (encryptionMode)
{
case EncryptionMode.XSalsa20Poly1305:
data = packet[HEADER_SIZE..];
return;
case EncryptionMode.XSalsa20Poly1305Suffix:
data = packet.Slice(HEADER_SIZE, packet.Length - HEADER_SIZE - Interop.SodiumNonceSize);
return;
case EncryptionMode.XSalsa20Poly1305Lite:
data = packet.Slice(HEADER_SIZE, packet.Length - HEADER_SIZE - 4);
break;
default:
throw new ArgumentException("Unsupported encryption mode.", nameof(encryptionMode));
}
}
/// <summary>
/// Disposes the Rtp.
/// </summary>
public void Dispose()
{
}
}
diff --git a/DisCatSharp.VoiceNext/Codec/Sodium.cs b/DisCatSharp.VoiceNext/Codec/Sodium.cs
index b1b5e03a0..128dfae56 100644
--- a/DisCatSharp.VoiceNext/Codec/Sodium.cs
+++ b/DisCatSharp.VoiceNext/Codec/Sodium.cs
@@ -1,293 +1,293 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// The sodium.
/// </summary>
internal sealed class Sodium : IDisposable
{
/// <summary>
/// Gets the supported modes.
/// </summary>
public static IReadOnlyDictionary<string, EncryptionMode> SupportedModes { get; }
/// <summary>
/// Gets the nonce size.
/// </summary>
public static int NonceSize => Interop.SodiumNonceSize;
/// <summary>
/// Gets the c s p r n g.
/// </summary>
private readonly RandomNumberGenerator _csprng;
/// <summary>
/// Gets the buffer.
/// </summary>
private readonly byte[] _buffer;
/// <summary>
/// Gets the key.
/// </summary>
private readonly ReadOnlyMemory<byte> _key;
/// <summary>
/// Initializes a new instance of the <see cref="Sodium"/> class.
/// </summary>
static Sodium()
{
SupportedModes = new ReadOnlyDictionary<string, EncryptionMode>(new Dictionary<string, EncryptionMode>()
{
["xsalsa20_poly1305_lite"] = EncryptionMode.XSalsa20Poly1305Lite,
["xsalsa20_poly1305_suffix"] = EncryptionMode.XSalsa20Poly1305Suffix,
["xsalsa20_poly1305"] = EncryptionMode.XSalsa20Poly1305
});
}
/// <summary>
/// Initializes a new instance of the <see cref="Sodium"/> class.
/// </summary>
/// <param name="key">The key.</param>
public Sodium(ReadOnlyMemory<byte> 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._buffer = new byte[Interop.SodiumNonceSize];
}
/// <summary>
/// Generates the nonce.
/// </summary>
/// <param name="rtpHeader">The rtp header.</param>
/// <param name="target">The target.</param>
public void GenerateNonce(ReadOnlySpan<byte> rtpHeader, Span<byte> 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..]);
}
/// <summary>
/// Generates the nonce.
/// </summary>
/// <param name="target">The target.</param>
public void GenerateNonce(Span<byte> 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._buffer.AsSpan().CopyTo(target);
}
/// <summary>
/// Generates the nonce.
/// </summary>
/// <param name="nonce">The nonce.</param>
/// <param name="target">The target.</param>
public void GenerateNonce(uint nonce, Span<byte> 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..]);
}
/// <summary>
/// Appends the nonce.
/// </summary>
/// <param name="nonce">The nonce.</param>
/// <param name="target">The target.</param>
/// <param name="encryptionMode">The encryption mode.</param>
public void AppendNonce(ReadOnlySpan<byte> nonce, Span<byte> target, EncryptionMode encryptionMode)
{
switch (encryptionMode)
{
case EncryptionMode.XSalsa20Poly1305:
return;
case EncryptionMode.XSalsa20Poly1305Suffix:
nonce.CopyTo(target[^12..]);
return;
case EncryptionMode.XSalsa20Poly1305Lite:
nonce[..4].CopyTo(target[^4..]);
return;
default:
throw new ArgumentException("Unsupported encryption mode.", nameof(encryptionMode));
}
}
/// <summary>
/// Gets the nonce.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="target">The target.</param>
/// <param name="encryptionMode">The encryption mode.</param>
public void GetNonce(ReadOnlySpan<byte> source, Span<byte> 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.XSalsa20Poly1305:
source[..12].CopyTo(target);
return;
case EncryptionMode.XSalsa20Poly1305Suffix:
source[^Interop.SodiumNonceSize..].CopyTo(target);
return;
case EncryptionMode.XSalsa20Poly1305Lite:
source[^4..].CopyTo(target);
return;
default:
throw new ArgumentException("Unsupported encryption mode.", nameof(encryptionMode));
}
}
/// <summary>
/// Encrypts the Sodium.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="target">The target.</param>
/// <param name="nonce">The nonce.</param>
public void Encrypt(ReadOnlySpan<byte> source, Span<byte> target, ReadOnlySpan<byte> 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}.");
}
/// <summary>
/// Decrypts the Sodium.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="target">The target.</param>
/// <param name="nonce">The nonce.</param>
public void Decrypt(ReadOnlySpan<byte> source, Span<byte> target, ReadOnlySpan<byte> 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}.");
}
/// <summary>
/// Disposes the Sodium.
/// </summary>
public void Dispose() => this._csprng.Dispose();
/// <summary>
/// Selects the mode.
/// </summary>
/// <param name="availableModes">The available modes.</param>
/// <returns>A KeyValuePair.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static KeyValuePair<string, EncryptionMode> SelectMode(IEnumerable<string> 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.");
}
/// <summary>
/// Calculates the target size.
/// </summary>
/// <param name="source">The source.</param>
/// <returns>An int.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateTargetSize(ReadOnlySpan<byte> source)
=> source.Length + Interop.SodiumMacSize;
/// <summary>
/// Calculates the source size.
/// </summary>
/// <param name="source">The source.</param>
/// <returns>An int.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CalculateSourceSize(ReadOnlySpan<byte> source)
=> source.Length - Interop.SodiumMacSize;
}
/// <summary>
/// Specifies an encryption mode to use with Sodium.
/// </summary>
public enum EncryptionMode
{
/// <summary>
/// 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.
/// </summary>
XSalsa20Poly1305Lite,
/// <summary>
/// The nonce consists of random bytes. It is appended at the end of a packet.
/// </summary>
XSalsa20Poly1305Suffix,
/// <summary>
/// The nonce consists of the RTP header. Nothing is appended to the packet.
/// </summary>
XSalsa20Poly1305
}
diff --git a/DisCatSharp.VoiceNext/DiscordClientExtensions.cs b/DisCatSharp.VoiceNext/DiscordClientExtensions.cs
index d2a9f1aba..27f1d8301 100644
--- a/DisCatSharp.VoiceNext/DiscordClientExtensions.cs
+++ b/DisCatSharp.VoiceNext/DiscordClientExtensions.cs
@@ -1,139 +1,139 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.Entities;
using DisCatSharp.Enums;
namespace DisCatSharp.VoiceNext;
/// <summary>
/// The discord client extensions.
/// </summary>
public static class DiscordClientExtensions
{
/// <summary>
/// Creates a new VoiceNext client with default settings.
/// </summary>
/// <param name="client">Discord client to create VoiceNext instance for.</param>
/// <returns>VoiceNext client instance.</returns>
public static VoiceNextExtension UseVoiceNext(this DiscordClient client)
=> UseVoiceNext(client, new VoiceNextConfiguration());
/// <summary>
/// Creates a new VoiceNext client with specified settings.
/// </summary>
/// <param name="client">Discord client to create VoiceNext instance for.</param>
/// <param name="config">Configuration for the VoiceNext client.</param>
/// <returns>VoiceNext client instance.</returns>
public static VoiceNextExtension UseVoiceNext(this DiscordClient client, VoiceNextConfiguration config)
{
if (client.GetExtension<VoiceNextExtension>() != null)
throw new InvalidOperationException("VoiceNext is already enabled for that client.");
var vnext = new VoiceNextExtension(config);
client.AddExtension(vnext);
return vnext;
}
/// <summary>
/// Creates new VoiceNext clients on all shards in a given sharded client.
/// </summary>
/// <param name="client">Discord sharded client to create VoiceNext instances for.</param>
/// <param name="config">Configuration for the VoiceNext clients.</param>
/// <returns>A dictionary of created VoiceNext clients.</returns>
public static async Task<IReadOnlyDictionary<int, VoiceNextExtension>> UseVoiceNextAsync(this DiscordShardedClient client, VoiceNextConfiguration config)
{
var modules = new Dictionary<int, VoiceNextExtension>();
await client.InitializeShardsAsync().ConfigureAwait(false);
foreach (var shard in client.ShardClients.Select(xkvp => xkvp.Value))
{
var vnext = shard.GetExtension<VoiceNextExtension>();
vnext ??= shard.UseVoiceNext(config);
modules[shard.ShardId] = vnext;
}
return new ReadOnlyDictionary<int, VoiceNextExtension>(modules);
}
/// <summary>
/// Gets the active instance of VoiceNext client for the DiscordClient.
/// </summary>
/// <param name="client">Discord client to get VoiceNext instance for.</param>
/// <returns>VoiceNext client instance.</returns>
public static VoiceNextExtension GetVoiceNext(this DiscordClient client)
=> client.GetExtension<VoiceNextExtension>();
/// <summary>
/// Retrieves a <see cref="VoiceNextExtension"/> instance for each shard.
/// </summary>
/// <param name="client">The shard client to retrieve <see cref="VoiceNextExtension"/> instances from.</param>
/// <returns>A dictionary containing <see cref="VoiceNextExtension"/> instances for each shard.</returns>
public static async Task<IReadOnlyDictionary<int, VoiceNextExtension>> GetVoiceNextAsync(this DiscordShardedClient client)
{
await client.InitializeShardsAsync().ConfigureAwait(false);
var extensions = new Dictionary<int, VoiceNextExtension>();
foreach (var shard in client.ShardClients.Values)
{
extensions.Add(shard.ShardId, shard.GetExtension<VoiceNextExtension>());
}
return new ReadOnlyDictionary<int, VoiceNextExtension>(extensions);
}
/// <summary>
/// Connects to this voice channel using VoiceNext.
/// </summary>
/// <param name="channel">Channel to connect to.</param>
/// <returns>If successful, the VoiceNext connection.</returns>
public static Task<VoiceNextConnection> ConnectAsync(this DiscordChannel channel)
{
if (channel == null)
throw new NullReferenceException();
if (channel.Guild == null)
throw new InvalidOperationException("VoiceNext can only be used with guild channels.");
if (channel.Type != ChannelType.Voice && channel.Type != ChannelType.Stage)
throw new InvalidOperationException("You can only connect to voice or stage channels.");
if (channel.Discord is not DiscordClient discord || discord == null)
throw new NullReferenceException();
var vnext = discord.GetVoiceNext();
if (vnext == null)
throw new InvalidOperationException("VoiceNext is not initialized for this Discord client.");
var vnc = vnext.GetConnection(channel.Guild);
return vnc != null
? throw new InvalidOperationException("VoiceNext is already connected in this guild.")
: vnext.ConnectAsync(channel);
}
}
diff --git a/DisCatSharp.VoiceNext/Entities/AudioSender.cs b/DisCatSharp.VoiceNext/Entities/AudioSender.cs
index 9a817e17e..7dd5a556e 100644
--- a/DisCatSharp.VoiceNext/Entities/AudioSender.cs
+++ b/DisCatSharp.VoiceNext/Entities/AudioSender.cs
@@ -1,167 +1,167 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// The audio sender.
/// </summary>
internal class AudioSender : IDisposable
{
// starting the counter a full wrap ahead handles an edge case where the VERY first packets
// we see are right around the wraparound line.
private ulong _sequenceBase = 1 << 16;
private SequenceWrapState _currentSequenceWrapState = SequenceWrapState.AssumeNextHighSequenceIsOutOfOrder;
private enum SequenceWrapState
{
Normal,
AssumeNextLowSequenceIsOverflow,
AssumeNextHighSequenceIsOutOfOrder,
}
/// <summary>
/// Gets the s s r c.
/// </summary>
public uint Ssrc { get; }
/// <summary>
/// Gets the id.
/// </summary>
public ulong Id => this.User?.Id ?? 0;
/// <summary>
/// Gets the decoder.
/// </summary>
public OpusDecoder Decoder { get; }
/// <summary>
/// Gets or sets the user.
/// </summary>
public DiscordUser User { get; set; } = null;
/// <summary>
/// Gets or sets the last sequence.
/// </summary>
public ulong? LastTrueSequence { get; set; } = null;
/// <summary>
/// Initializes a new instance of the <see cref="AudioSender"/> class.
/// </summary>
/// <param name="ssrc">The ssrc.</param>
/// <param name="decoder">The decoder.</param>
public AudioSender(uint ssrc, OpusDecoder decoder)
{
this.Ssrc = ssrc;
this.Decoder = decoder;
}
/// <summary>
/// Disposes .
/// </summary>
public void Dispose() => this.Decoder?.Dispose();
/// <summary>
/// Accepts the 16-bit sequence number from the next RTP header in the associated stream and
/// uses heuristics to (attempt to) convert it into a 64-bit counter that takes into account
/// overflow wrapping around to zero.
/// <para/>
/// This method only works properly if it is called for <b>every</b> sequence number that we
/// see in the stream.
/// </summary>
/// <param name="originalSequence">
/// The 16-bit sequence number from the next RTP header.
/// </param>
/// <returns>
/// Our best-effort guess of the value that <paramref name="originalSequence"/> <b>would</b>
/// have been, if the server had given us a 64-bit integer instead of a 16-bit one.
/// </returns>
public ulong GetTrueSequenceAfterWrapping(ushort originalSequence)
{
// section off a smallish zone at either end of the 16-bit integer range. whenever the
// sequence numbers creep into the higher zone, we start keeping an eye out for when
// sequence numbers suddenly start showing up in the lower zone. we expect this to mean
// that the sequence numbers overflowed and wrapped around. there's a bit of a balance
// when determining an appropriate size for the buffer zone: if it's too small, then a
// brief (but recoverable) network interruption could cause us to miss the lead-up to
// the overflow. on the other hand, if it's too large, then such a network interruption
// could cause us to misinterpret a normal sequence for one that's out-of-order.
//
// at 20 milliseconds per packet, 3,000 packets means that the buffer zone is one minute
// on either side. in other words, as long as we're getting packets delivered within a
// minute or so of when they should be, the 64-bit sequence numbers coming out of this
// method will be perfectly consistent with reality.
const ushort OVERFLOW_BUFFER_ZONE = 3_000;
const ushort LOW_THRESHOLD = OVERFLOW_BUFFER_ZONE;
const ushort HIGH_THRESHOLD = ushort.MaxValue - OVERFLOW_BUFFER_ZONE;
ulong wrappingAdjustment = 0;
switch (this._currentSequenceWrapState)
{
case SequenceWrapState.Normal when originalSequence > HIGH_THRESHOLD:
// we were going about our business up to this point. the sequence numbers have
// gotten a bit high, so let's start looking out for any sequence numbers that
// are suddenly WAY lower than where they are right now.
this._currentSequenceWrapState = SequenceWrapState.AssumeNextLowSequenceIsOverflow;
break;
case SequenceWrapState.AssumeNextLowSequenceIsOverflow when originalSequence < LOW_THRESHOLD:
// we had seen some sequence numbers that got a bit high, and now we see this
// sequence number that's WAY lower than before. this is a classic sign that
// the sequence numbers have wrapped around. in order to present a consistently
// increasing "true" sequence number, add another 65,536 and keep counting. if
// we see another high sequence number in the near future, assume that it's a
// packet coming in out of order.
this._sequenceBase += 1 << 16;
this._currentSequenceWrapState = SequenceWrapState.AssumeNextHighSequenceIsOutOfOrder;
break;
case SequenceWrapState.AssumeNextHighSequenceIsOutOfOrder when originalSequence > HIGH_THRESHOLD:
// we're seeing some high sequence numbers EITHER at the beginning of the stream
// OR very close to the time when we saw some very low sequence numbers. in the
// latter case, it happened because the packets came in out of order, right when
// the sequence numbers wrapped around. in the former case, we MIGHT be in the
// same kind of situation (we can't tell yet), so we err on the side of caution
// and burn a full cycle before we start counting so that we can handle both
// cases with the exact same adjustment.
wrappingAdjustment = 1 << 16;
break;
case SequenceWrapState.AssumeNextHighSequenceIsOutOfOrder when originalSequence > LOW_THRESHOLD:
// EITHER we're at the very beginning of the stream OR very close to the time
// when we saw some very low sequence numbers. either way, we're out of the
// zones where we should consider very low sequence numbers to come AFTER very
// high ones, so we can go back to normal now.
this._currentSequenceWrapState = SequenceWrapState.Normal;
break;
}
return this._sequenceBase + originalSequence - wrappingAdjustment;
}
}
diff --git a/DisCatSharp.VoiceNext/Entities/VoiceDispatch.cs b/DisCatSharp.VoiceNext/Entities/VoiceDispatch.cs
index f409c139f..bcb9cd056 100644
--- a/DisCatSharp.VoiceNext/Entities/VoiceDispatch.cs
+++ b/DisCatSharp.VoiceNext/Entities/VoiceDispatch.cs
@@ -1,55 +1,55 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// The voice dispatch.
/// </summary>
internal sealed class VoiceDispatch
{
/// <summary>
/// Gets or sets the op code.
/// </summary>
[JsonProperty("op")]
public int OpCode { get; set; }
/// <summary>
/// Gets or sets the payload.
/// </summary>
[JsonProperty("d")]
public object Payload { get; set; }
/// <summary>
/// Gets or sets the sequence.
/// </summary>
[JsonProperty("s", NullValueHandling = NullValueHandling.Ignore)]
public int? Sequence { get; set; }
/// <summary>
/// Gets or sets the event name.
/// </summary>
[JsonProperty("t", NullValueHandling = NullValueHandling.Ignore)]
public string EventName { get; set; }
}
diff --git a/DisCatSharp.VoiceNext/Entities/VoiceIdentifyPayload.cs b/DisCatSharp.VoiceNext/Entities/VoiceIdentifyPayload.cs
index f90d38c0a..c2e3791fb 100644
--- a/DisCatSharp.VoiceNext/Entities/VoiceIdentifyPayload.cs
+++ b/DisCatSharp.VoiceNext/Entities/VoiceIdentifyPayload.cs
@@ -1,55 +1,55 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// The voice identify payload.
/// </summary>
internal sealed class VoiceIdentifyPayload
{
/// <summary>
/// Gets or sets the server id.
/// </summary>
[JsonProperty("server_id")]
public ulong ServerId { get; set; }
/// <summary>
/// Gets or sets the user id.
/// </summary>
[JsonProperty("user_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? UserId { get; set; }
/// <summary>
/// Gets or sets the session id.
/// </summary>
[JsonProperty("session_id")]
public string SessionId { get; set; }
/// <summary>
/// Gets or sets the token.
/// </summary>
[JsonProperty("token")]
public string Token { get; set; }
}
diff --git a/DisCatSharp.VoiceNext/Entities/VoicePacket.cs b/DisCatSharp.VoiceNext/Entities/VoicePacket.cs
index ce14c2072..3d8ce3598 100644
--- a/DisCatSharp.VoiceNext/Entities/VoicePacket.cs
+++ b/DisCatSharp.VoiceNext/Entities/VoicePacket.cs
@@ -1,54 +1,54 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.VoiceNext.Entities;
internal struct VoicePacket
{
/// <summary>
/// Gets the bytes.
/// </summary>
public ReadOnlyMemory<byte> Bytes { get; }
/// <summary>
/// Gets the millisecond duration.
/// </summary>
public int MillisecondDuration { get; }
/// <summary>
/// Gets or sets a value indicating whether is silence.
/// </summary>
public bool IsSilence { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="VoicePacket"/> class.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="msDuration">The ms duration.</param>
/// <param name="isSilence">If true, is silence.</param>
public VoicePacket(ReadOnlyMemory<byte> bytes, int msDuration, bool isSilence = false)
{
this.Bytes = bytes;
this.MillisecondDuration = msDuration;
this.IsSilence = isSilence;
}
}
diff --git a/DisCatSharp.VoiceNext/Entities/VoiceReadyPayload.cs b/DisCatSharp.VoiceNext/Entities/VoiceReadyPayload.cs
index c4babedac..0bfcdb9f7 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-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// The voice ready payload.
/// </summary>
internal sealed class VoiceReadyPayload
{
/// <summary>
/// Gets or sets the s s r c.
/// </summary>
[JsonProperty("ssrc")]
public uint Ssrc { get; set; }
/// <summary>
/// Gets or sets the address.
/// </summary>
[JsonProperty("ip")]
public string Address { get; set; }
/// <summary>
/// Gets or sets the port.
/// </summary>
[JsonProperty("port")]
public ushort Port { get; set; }
/// <summary>
/// Gets or sets the modes.
/// </summary>
[JsonProperty("modes")]
public IReadOnlyList<string> Modes { get; set; }
/// <summary>
/// Gets or sets the heartbeat interval.
/// </summary>
[JsonProperty("heartbeat_interval")]
public int HeartbeatInterval { get; set; }
}
diff --git a/DisCatSharp.VoiceNext/Entities/VoiceSelectProtocolPayload.cs b/DisCatSharp.VoiceNext/Entities/VoiceSelectProtocolPayload.cs
index ecc967498..900ff08cb 100644
--- a/DisCatSharp.VoiceNext/Entities/VoiceSelectProtocolPayload.cs
+++ b/DisCatSharp.VoiceNext/Entities/VoiceSelectProtocolPayload.cs
@@ -1,43 +1,43 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// The voice select protocol payload.
/// </summary>
internal sealed class VoiceSelectProtocolPayload
{
/// <summary>
/// Gets or sets the protocol.
/// </summary>
[JsonProperty("protocol")]
public string Protocol { get; set; }
/// <summary>
/// Gets or sets the data.
/// </summary>
[JsonProperty("data")]
public VoiceSelectProtocolPayloadData Data { get; set; }
}
diff --git a/DisCatSharp.VoiceNext/Entities/VoiceSelectProtocolPayloadData.cs b/DisCatSharp.VoiceNext/Entities/VoiceSelectProtocolPayloadData.cs
index bf67f27d1..7255da2a7 100644
--- a/DisCatSharp.VoiceNext/Entities/VoiceSelectProtocolPayloadData.cs
+++ b/DisCatSharp.VoiceNext/Entities/VoiceSelectProtocolPayloadData.cs
@@ -1,49 +1,49 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// The voice select protocol payload data.
/// </summary>
internal class VoiceSelectProtocolPayloadData
{
/// <summary>
/// Gets or sets the address.
/// </summary>
[JsonProperty("address")]
public string Address { get; set; }
/// <summary>
/// Gets or sets the port.
/// </summary>
[JsonProperty("port")]
public ushort Port { get; set; }
/// <summary>
/// Gets or sets the mode.
/// </summary>
[JsonProperty("mode")]
public string Mode { get; set; }
}
diff --git a/DisCatSharp.VoiceNext/Entities/VoiceServerUpdatePayload.cs b/DisCatSharp.VoiceNext/Entities/VoiceServerUpdatePayload.cs
index 572a77e2d..66ae3ecec 100644
--- a/DisCatSharp.VoiceNext/Entities/VoiceServerUpdatePayload.cs
+++ b/DisCatSharp.VoiceNext/Entities/VoiceServerUpdatePayload.cs
@@ -1,49 +1,49 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// The voice server update payload.
/// </summary>
internal sealed class VoiceServerUpdatePayload
{
/// <summary>
/// Gets or sets the token.
/// </summary>
[JsonProperty("token")]
public string Token { get; set; }
/// <summary>
/// Gets or sets the guild id.
/// </summary>
[JsonProperty("guild_id")]
public ulong GuildId { get; set; }
/// <summary>
/// Gets or sets the endpoint.
/// </summary>
[JsonProperty("endpoint")]
public string Endpoint { get; set; }
}
diff --git a/DisCatSharp.VoiceNext/Entities/VoiceSessionDescriptionPayload.cs b/DisCatSharp.VoiceNext/Entities/VoiceSessionDescriptionPayload.cs
index 692e0deac..10cb58407 100644
--- a/DisCatSharp.VoiceNext/Entities/VoiceSessionDescriptionPayload.cs
+++ b/DisCatSharp.VoiceNext/Entities/VoiceSessionDescriptionPayload.cs
@@ -1,43 +1,43 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// The voice session description payload.
/// </summary>
internal sealed class VoiceSessionDescriptionPayload
{
/// <summary>
/// Gets or sets the secret key.
/// </summary>
[JsonProperty("secret_key")]
public byte[] SecretKey { get; set; }
/// <summary>
/// Gets or sets the mode.
/// </summary>
[JsonProperty("mode")]
public string Mode { get; set; }
}
diff --git a/DisCatSharp.VoiceNext/Entities/VoiceSpeakingPayload.cs b/DisCatSharp.VoiceNext/Entities/VoiceSpeakingPayload.cs
index 80ce120be..945cef17f 100644
--- a/DisCatSharp.VoiceNext/Entities/VoiceSpeakingPayload.cs
+++ b/DisCatSharp.VoiceNext/Entities/VoiceSpeakingPayload.cs
@@ -1,55 +1,55 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// The voice speaking payload.
/// </summary>
internal sealed class VoiceSpeakingPayload
{
/// <summary>
/// Gets or sets a value indicating whether speaking.
/// </summary>
[JsonProperty("speaking")]
public bool Speaking { get; set; }
/// <summary>
/// Gets or sets the delay.
/// </summary>
[JsonProperty("delay", NullValueHandling = NullValueHandling.Ignore)]
public int? Delay { get; set; }
/// <summary>
/// Gets or sets the s s r c.
/// </summary>
[JsonProperty("ssrc", NullValueHandling = NullValueHandling.Ignore)]
public uint? Ssrc { get; set; }
/// <summary>
/// Gets or sets the user id.
/// </summary>
[JsonProperty("user_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? UserId { get; set; }
}
diff --git a/DisCatSharp.VoiceNext/Entities/VoiceStateUpdatePayload.cs b/DisCatSharp.VoiceNext/Entities/VoiceStateUpdatePayload.cs
index 4d6cc4ecc..edd207147 100644
--- a/DisCatSharp.VoiceNext/Entities/VoiceStateUpdatePayload.cs
+++ b/DisCatSharp.VoiceNext/Entities/VoiceStateUpdatePayload.cs
@@ -1,67 +1,67 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// The voice state update payload.
/// </summary>
internal sealed class VoiceStateUpdatePayload
{
/// <summary>
/// Gets or sets the guild id.
/// </summary>
[JsonProperty("guild_id")]
public ulong GuildId { get; set; }
/// <summary>
/// Gets or sets the channel id.
/// </summary>
[JsonProperty("channel_id")]
public ulong? ChannelId { get; set; }
/// <summary>
/// Gets or sets the user id.
/// </summary>
[JsonProperty("user_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? UserId { get; set; }
/// <summary>
/// Gets or sets the session id.
/// </summary>
[JsonProperty("session_id", NullValueHandling = NullValueHandling.Ignore)]
public string SessionId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether deafened.
/// </summary>
[JsonProperty("self_deaf")]
public bool Deafened { get; set; }
/// <summary>
/// Gets or sets a value indicating whether muted.
/// </summary>
[JsonProperty("self_mute")]
public bool Muted { get; set; }
}
diff --git a/DisCatSharp.VoiceNext/Entities/VoiceUserJoinPayload.cs b/DisCatSharp.VoiceNext/Entities/VoiceUserJoinPayload.cs
index 31f89d2b2..ee664dc3b 100644
--- a/DisCatSharp.VoiceNext/Entities/VoiceUserJoinPayload.cs
+++ b/DisCatSharp.VoiceNext/Entities/VoiceUserJoinPayload.cs
@@ -1,43 +1,43 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// The voice user join payload.
/// </summary>
internal sealed class VoiceUserJoinPayload
{
/// <summary>
/// Gets the user id.
/// </summary>
[JsonProperty("user_id")]
public ulong UserId { get; private set; }
/// <summary>
/// Gets the s s r c.
/// </summary>
[JsonProperty("audio_ssrc")]
public uint Ssrc { get; private set; }
}
diff --git a/DisCatSharp.VoiceNext/Entities/VoiceUserLeavePayload.cs b/DisCatSharp.VoiceNext/Entities/VoiceUserLeavePayload.cs
index 308f95c97..f16102e48 100644
--- a/DisCatSharp.VoiceNext/Entities/VoiceUserLeavePayload.cs
+++ b/DisCatSharp.VoiceNext/Entities/VoiceUserLeavePayload.cs
@@ -1,37 +1,37 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// The voice user leave payload.
/// </summary>
internal sealed class VoiceUserLeavePayload
{
/// <summary>
/// Gets or sets the user id.
/// </summary>
[JsonProperty("user_id")]
public ulong UserId { get; set; }
}
diff --git a/DisCatSharp.VoiceNext/EventArgs/VoiceReceiveEventArgs.cs b/DisCatSharp.VoiceNext/EventArgs/VoiceReceiveEventArgs.cs
index 662d740fe..d7c62411c 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-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for VoiceReceived events.
/// </summary>
public class VoiceReceiveEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the SSRC of the audio source.
/// </summary>
public uint Ssrc { get; internal set; }
#pragma warning disable CS8632
/// <summary>
/// Gets the user that sent the audio data.
/// </summary>
public DiscordUser? User { get; internal set; }
#pragma warning restore
/// <summary>
/// Gets the received voice data, decoded to PCM format.
/// </summary>
public ReadOnlyMemory<byte> PcmData { get; internal set; }
/// <summary>
/// Gets the received voice data, in Opus format. Note that for packets that were lost and/or compensated for, this will be empty.
/// </summary>
public ReadOnlyMemory<byte> OpusData { get; internal set; }
/// <summary>
/// Gets the format of the received PCM data.
/// <para>
/// Important: This isn't always the format set in <see cref="VoiceNextConfiguration.AudioFormat"/>, and depends on the audio data received.
/// </para>
/// </summary>
public AudioFormat AudioFormat { get; internal set; }
/// <summary>
/// Gets the millisecond duration of the PCM audio sample.
/// </summary>
public int AudioDuration { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="VoiceReceiveEventArgs"/> class.
/// </summary>
internal VoiceReceiveEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp.VoiceNext/EventArgs/VoiceUserJoinEventArgs.cs b/DisCatSharp.VoiceNext/EventArgs/VoiceUserJoinEventArgs.cs
index d6d14699e..7fe3fed69 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-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Arguments for <see cref="VoiceNextConnection.UserJoined"/>.
/// </summary>
public sealed class VoiceUserJoinEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the user who left.
/// </summary>
public DiscordUser User { get; internal set; }
/// <summary>
/// Gets the SSRC of the user who joined.
/// </summary>
public uint Ssrc { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="VoiceUserJoinEventArgs"/> class.
/// </summary>
internal VoiceUserJoinEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp.VoiceNext/EventArgs/VoiceUserLeaveEventArgs.cs b/DisCatSharp.VoiceNext/EventArgs/VoiceUserLeaveEventArgs.cs
index a8b786610..3f2b7d76d 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-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Arguments for <see cref="VoiceNextConnection.UserLeft"/>.
/// </summary>
public sealed class VoiceUserLeaveEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the user who left.
/// </summary>
public DiscordUser User { get; internal set; }
/// <summary>
/// Gets the SSRC of the user who left.
/// </summary>
public uint Ssrc { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="VoiceUserLeaveEventArgs"/> class.
/// </summary>
internal VoiceUserLeaveEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp.VoiceNext/GlobalSuppressions.cs b/DisCatSharp.VoiceNext/GlobalSuppressions.cs
index 2ee039b08..9d068be2a 100644
--- a/DisCatSharp.VoiceNext/GlobalSuppressions.cs
+++ b/DisCatSharp.VoiceNext/GlobalSuppressions.cs
@@ -1,51 +1,51 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.VoiceNextConnection.Dispose")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.VoiceNextConnection.VoiceWS_SocketMessage(DisCatSharp.Net.WebSocket.IWebSocketClient,DisCatSharp.EventArgs.SocketMessageEventArgs)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.VoiceNextConnection.WsSendAsync(System.String)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Performance", "CA1806:Do not ignore method results", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.Codec.Interop.OpusGetLastPacketDuration(System.IntPtr,System.Int32@)")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.VoiceNextConnection.HandleDispatch(Newtonsoft.Json.Linq.JObject)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.VoiceNextConnection.ProcessKeepalive(System.Byte[])")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.VoiceNextConnection.Stage1(DisCatSharp.VoiceNext.Entities.VoiceReadyPayload)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.VoiceNextConnection.Stage2(DisCatSharp.VoiceNext.Entities.VoiceSessionDescriptionPayload)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.VoiceNextConnection.VoiceSenderTask~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.VoiceNextConnection.VoiceWS_SocketClosed(DisCatSharp.Net.WebSocket.IWebSocketClient,DisCatSharp.EventArgs.SocketCloseEventArgs)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.Codec.Opus.GetLastPacketSampleCount(DisCatSharp.VoiceNext.Codec.OpusDecoder)~System.Int32")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.Codec.Opus.ProcessPacketLoss(DisCatSharp.VoiceNext.Codec.OpusDecoder,System.Int32,System.Span{System.Byte}@)")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.Codec.Rtp.CalculatePacketSize(System.Int32,DisCatSharp.VoiceNext.Codec.EncryptionMode)~System.Int32")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.Codec.Rtp.DecodeHeader(System.ReadOnlySpan{System.Byte},System.UInt16@,System.UInt32@,System.UInt32@,System.Boolean@)")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.Codec.Rtp.EncodeHeader(System.UInt16,System.UInt32,System.UInt32,System.Span{System.Byte})")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.Codec.Rtp.GetDataFromPacket(System.ReadOnlySpan{System.Byte},System.ReadOnlySpan{System.Byte}@,DisCatSharp.VoiceNext.Codec.EncryptionMode)")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.Codec.Rtp.IsRtpHeader(System.ReadOnlySpan{System.Byte})~System.Boolean")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.Codec.Sodium.AppendNonce(System.ReadOnlySpan{System.Byte},System.Span{System.Byte},DisCatSharp.VoiceNext.Codec.EncryptionMode)")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.Codec.Sodium.GenerateNonce(System.ReadOnlySpan{System.Byte},System.Span{System.Byte})")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.Codec.Sodium.GenerateNonce(System.UInt32,System.Span{System.Byte})")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.Codec.Sodium.GetNonce(System.ReadOnlySpan{System.Byte},System.Span{System.Byte},DisCatSharp.VoiceNext.Codec.EncryptionMode)")]
[assembly: SuppressMessage("Performance", "CA1806:Do not ignore method results", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.VoiceNext.Interop.Bindings.GetLastPacketDuration(System.IntPtr,System.Int32@)")]
diff --git a/DisCatSharp.VoiceNext/IVoiceFilter.cs b/DisCatSharp.VoiceNext/IVoiceFilter.cs
index 207a8e329..1a032698c 100644
--- a/DisCatSharp.VoiceNext/IVoiceFilter.cs
+++ b/DisCatSharp.VoiceNext/IVoiceFilter.cs
@@ -1,39 +1,39 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.VoiceNext;
/// <summary>
/// Represents a filter for PCM data. PCM data submitted through a <see cref="VoiceTransmitSink"/> will be sent through all installed instances of <see cref="IVoiceFilter"/> first.
/// </summary>
public interface IVoiceFilter
{
/// <summary>
/// Transforms the supplied PCM data using this filter.
/// </summary>
/// <param name="pcmData">PCM data to transform. The transformation happens in-place.</param>
/// <param name="pcmFormat">Format of the supplied PCM data.</param>
/// <param name="duration">Millisecond duration of the supplied PCM data.</param>
void Transform(Span<short> pcmData, AudioFormat pcmFormat, int duration);
}
diff --git a/DisCatSharp.VoiceNext/Interop/Bindings.cs b/DisCatSharp.VoiceNext/Interop/Bindings.cs
index 3698be98f..21636dea1 100644
--- a/DisCatSharp.VoiceNext/Interop/Bindings.cs
+++ b/DisCatSharp.VoiceNext/Interop/Bindings.cs
@@ -1,155 +1,155 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Runtime.InteropServices;
namespace DisCatSharp.VoiceNext.Interop
{
internal static unsafe class Bindings
{
[DllImport("libopus", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr opus_encoder_create(int samplingRate, int channels, int application, out OpusError error);
[DllImport("libopus", CallingConvention = CallingConvention.Cdecl)]
private static extern void opus_encoder_destroy(IntPtr encoder);
[DllImport("libopus", CallingConvention = CallingConvention.Cdecl)]
private static extern int opus_encode(IntPtr encoder, byte* pcm, int frameSize, byte* data, int maxDataBytes);
[DllImport("libopus", CallingConvention = CallingConvention.Cdecl)]
private static extern OpusError opus_encoder_ctl(IntPtr encoder, OpusControl ctl, int value);
[DllImport("libopus", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr opus_decoder_create(int sampleRate, int channels, out OpusError error);
[DllImport("libopus", CallingConvention = CallingConvention.Cdecl)]
private static extern void opus_decoder_destroy(IntPtr decoder);
[DllImport("libopus", CallingConvention = CallingConvention.Cdecl)]
private static extern int opus_decode(IntPtr decoder, byte* opusData, int opusDataLength, byte* data, int frameSize, int decodeFec);
[DllImport("libopus", CallingConvention = CallingConvention.Cdecl)]
private static extern int opus_packet_get_nb_channels(byte* data);
[DllImport("libopus", CallingConvention = CallingConvention.Cdecl)]
private static extern int opus_packet_get_nb_frames(byte* data, int length);
[DllImport("libopus", CallingConvention = CallingConvention.Cdecl)]
private static extern int opus_packet_get_samples_per_frame(byte* data, int samplingRate);
[DllImport("libopus", CallingConvention = CallingConvention.Cdecl)]
private static extern int opus_decoder_ctl(IntPtr decoder, OpusControl ctl, out int value);
public static IntPtr CreateEncoder(int sampleRate, int channelCount, int application)
{
var encoder = opus_encoder_create(sampleRate, channelCount, application, out var error);
return error == OpusError.Ok ? encoder : throw new Exception($"Failed to instantiate Opus encoder: {error} ({(int)error})");
}
public static void SetEncoderOption(IntPtr encoder, OpusControl option, int value)
{
var error = OpusError.Ok;
if ((error = opus_encoder_ctl(encoder, option, value)) != OpusError.Ok)
throw new Exception($"Failed to set Opus encoder option: ${error} ({(int)error})");
}
public static void Encode(IntPtr encoder, ReadOnlySpan<byte> pcm, int frameSize, ref Span<byte> data)
{
var length = 0;
fixed (byte* pcmPointer = pcm)
fixed (byte* dataPointer = data)
length = opus_encode(encoder, pcmPointer, frameSize, dataPointer, data.Length);
if (length < 0)
{
var error = (OpusError)length;
throw new Exception($"Failed to encode PCM data: {error} ({length})");
}
data = data[..length];
}
public static IntPtr CreateDecoder(int sampleRate, int channelCount)
{
var decoder = opus_decoder_create(sampleRate, channelCount, out var error);
return error == OpusError.Ok ? decoder : throw new Exception($"Failed to instantiate Opus decoder: {error} ({(int)error})");
}
public static int Decode(IntPtr decoder, ReadOnlySpan<byte> data, int frameSize, Span<byte> pcm, bool useFec)
{
var length = 0;
fixed (byte* dataPointer = data)
fixed (byte* pcmPointer = pcm)
length = opus_decode(decoder, dataPointer, data.Length, pcmPointer, frameSize, useFec ? 1 : 0);
if (length < 0)
{
var error = (OpusError)length;
throw new Exception($"Failed to decode PCM data: {error} ({length})");
}
return length;
}
public static int Decode(IntPtr decoder, int frameSize, Span<byte> pcm)
{
var length = 0;
fixed (byte* pcmPointer = pcm)
length = opus_decode(decoder, null, 0, pcmPointer, frameSize, 1);
if (length < 0)
{
var error = (OpusError)length;
throw new Exception($"Failed to decode PCM data: {error} ({length})");
}
return length;
}
public static OpusPacketMetrics GetPacketMetrics(ReadOnlySpan<byte> data, int samplingRate)
{
int channels, frames, samplesPerFrame;
fixed (byte* dataPointer = data)
{
frames = opus_packet_get_nb_frames(dataPointer, data.Length);
samplesPerFrame = opus_packet_get_samples_per_frame(dataPointer, samplingRate);
channels = opus_packet_get_nb_channels(dataPointer);
}
return new()
{
ChannelCount = channels,
FrameCount = frames,
SamplesPerFrame = samplesPerFrame,
FrameSize = frames * samplesPerFrame
};
}
public static void GetLastPacketDuration(IntPtr decoder, out int sampleCount)
=> opus_decoder_ctl(decoder, OpusControl.GetLastPacketDuration, out sampleCount);
}
}
diff --git a/DisCatSharp.VoiceNext/Interop/OpusControl.cs b/DisCatSharp.VoiceNext/Interop/OpusControl.cs
index d609b0876..f11731e15 100644
--- a/DisCatSharp.VoiceNext/Interop/OpusControl.cs
+++ b/DisCatSharp.VoiceNext/Interop/OpusControl.cs
@@ -1,35 +1,35 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.VoiceNext.Interop
{
internal enum OpusControl : int
{
SetBitrate = 4002,
SetBandwidth = 4008,
SetInBandFec = 4012,
SetPacketLossPercent = 4014,
SetSignal = 4024,
ResetState = 4028,
GetLastPacketDuration = 4039
}
}
diff --git a/DisCatSharp.VoiceNext/Interop/OpusError.cs b/DisCatSharp.VoiceNext/Interop/OpusError.cs
index ec7b6a4e9..82a72eec8 100644
--- a/DisCatSharp.VoiceNext/Interop/OpusError.cs
+++ b/DisCatSharp.VoiceNext/Interop/OpusError.cs
@@ -1,36 +1,36 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.VoiceNext.Interop
{
internal enum OpusError
{
Ok = 0,
InvalidArgument = -1,
BufferTooSmall = -2,
InternalError = -3,
CorruptedStream = -4,
RequestNotImplemented = -5,
InvalidState = -6,
MemoryAllocationFailed = -7
}
}
diff --git a/DisCatSharp.VoiceNext/Interop/OpusPacketMetrics.cs b/DisCatSharp.VoiceNext/Interop/OpusPacketMetrics.cs
index c3de5ae40..5b9810faa 100644
--- a/DisCatSharp.VoiceNext/Interop/OpusPacketMetrics.cs
+++ b/DisCatSharp.VoiceNext/Interop/OpusPacketMetrics.cs
@@ -1,35 +1,35 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.VoiceNext.Interop
{
internal struct OpusPacketMetrics
{
public int ChannelCount { get; set; }
public int FrameCount { get; set; }
public int SamplesPerFrame { get; set; }
public int FrameSize { get; set; }
}
}
diff --git a/DisCatSharp.VoiceNext/Interop/OpusSignal.cs b/DisCatSharp.VoiceNext/Interop/OpusSignal.cs
index ffef542c7..78545ca3c 100644
--- a/DisCatSharp.VoiceNext/Interop/OpusSignal.cs
+++ b/DisCatSharp.VoiceNext/Interop/OpusSignal.cs
@@ -1,31 +1,31 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.VoiceNext.Interop
{
internal enum OpusSignal : int
{
Auto = -1000,
Voice = 3000,
Music = 3002
}
}
diff --git a/DisCatSharp.VoiceNext/Properties/AssemblyProperties.cs b/DisCatSharp.VoiceNext/Properties/AssemblyProperties.cs
index 43392eec5..6d97072cd 100644
--- a/DisCatSharp.VoiceNext/Properties/AssemblyProperties.cs
+++ b/DisCatSharp.VoiceNext/Properties/AssemblyProperties.cs
@@ -1,47 +1,47 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("DisCatSharp.ApplicationCommands")]
[assembly: InternalsVisibleTo("DisCatSharp.CommandsNext")]
[assembly: InternalsVisibleTo("DisCatSharp.Common")]
[assembly: InternalsVisibleTo("DisCatSharp.Configuration")]
[assembly: InternalsVisibleTo("DisCatSharp.Experimental")]
[assembly: InternalsVisibleTo("DisCatSharp.Configuration.Tests")]
[assembly: InternalsVisibleTo("DisCatSharp.Hosting")]
[assembly: InternalsVisibleTo("DisCatSharp.Hosting.DependencyInjection")]
[assembly: InternalsVisibleTo("DisCatSharp.Hosting.Tests")]
[assembly: InternalsVisibleTo("DisCatSharp.Interactivity")]
[assembly: InternalsVisibleTo("DisCatSharp.Lavalink")]
[assembly: InternalsVisibleTo("DisCatSharp.Phabricator")]
[assembly: InternalsVisibleTo("DisCatSharp.Support")]
[assembly: InternalsVisibleTo("DisCatSharp.Test")]
[assembly: InternalsVisibleTo("DisCatSharp")]
[assembly: InternalsVisibleTo("DisCatSharp.VoiceNext.Natives")]
[assembly: InternalsVisibleTo("Nyaw")]
[assembly: InternalsVisibleTo("DisCatSharp.DevTools")]
[assembly: InternalsVisibleTo("DisCatSharp.DocsGenerator")]
[assembly: InternalsVisibleTo("DisCatSharp.StaffApps")]
[assembly: InternalsVisibleTo("Microsoft.DocAsCode")]
[assembly: InternalsVisibleTo("Microsoft.DocAsCode.Metadata.ManagedReference")]
diff --git a/DisCatSharp.VoiceNext/RawVoicePacket.cs b/DisCatSharp.VoiceNext/RawVoicePacket.cs
index b4793fd26..921a90ac1 100644
--- a/DisCatSharp.VoiceNext/RawVoicePacket.cs
+++ b/DisCatSharp.VoiceNext/RawVoicePacket.cs
@@ -1,61 +1,61 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.VoiceNext;
internal readonly struct RawVoicePacket
{
/// <summary>
/// Initializes a new instance of the <see cref="RawVoicePacket"/> class.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="duration">The duration.</param>
/// <param name="silence">If true, silence.</param>
public RawVoicePacket(Memory<byte> bytes, int duration, bool silence)
{
this.Bytes = bytes;
this.Duration = duration;
this.Silence = silence;
this.RentedBuffer = null;
}
/// <summary>
/// Initializes a new instance of the <see cref="RawVoicePacket"/> class.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="duration">The duration.</param>
/// <param name="silence">If true, silence.</param>
/// <param name="rentedBuffer">The rented buffer.</param>
public RawVoicePacket(Memory<byte> bytes, int duration, bool silence, byte[] rentedBuffer)
: this(bytes, duration, silence)
{
this.RentedBuffer = rentedBuffer;
}
public readonly Memory<byte> Bytes;
public readonly int Duration;
public readonly bool Silence;
public readonly byte[] RentedBuffer;
}
diff --git a/DisCatSharp.VoiceNext/StreamExtensions.cs b/DisCatSharp.VoiceNext/StreamExtensions.cs
index 7c0c62b11..6ab6dfbdb 100644
--- a/DisCatSharp.VoiceNext/StreamExtensions.cs
+++ b/DisCatSharp.VoiceNext/StreamExtensions.cs
@@ -1,71 +1,71 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.IO;
using System.Threading;
using System.Threading.Tasks;
namespace DisCatSharp.VoiceNext;
/// <summary>
/// The stream extensions.
/// </summary>
public static class StreamExtensions
{
/// <summary>
/// Asynchronously reads the bytes from the current stream and writes them to the specified <see cref="VoiceTransmitSink"/>.
/// </summary>
/// <param name="source">The source <see cref="Stream"/></param>
/// <param name="destination">The target <see cref="VoiceTransmitSink"/></param>
/// <param name="bufferSize">The size, in bytes, of the buffer. This value must be greater than zero. If <see langword="null"/>, defaults to the packet size specified by <paramref name="destination"/>.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns></returns>
public static async Task CopyToAsync(this Stream source, VoiceTransmitSink destination, int? bufferSize = null, CancellationToken cancellationToken = default)
{
// adapted from CoreFX
// https://source.dot.net/#System.Private.CoreLib/Stream.cs,8048a9680abdd13b
if (source is null)
throw new ArgumentNullException(nameof(source));
if (destination is null)
throw new ArgumentNullException(nameof(destination));
if (bufferSize != null && bufferSize <= 0)
throw new ArgumentOutOfRangeException(nameof(bufferSize), bufferSize, "bufferSize cannot be less than or equal to zero");
var bufferLength = bufferSize ?? destination.SampleLength;
var buffer = ArrayPool<byte>.Shared.Rent(bufferLength);
try
{
int bytesRead;
while ((bytesRead = await source.ReadAsync(buffer.AsMemory(0, bufferLength), cancellationToken).ConfigureAwait(false)) != 0)
{
await destination.WriteAsync(new ReadOnlyMemory<byte>(buffer, 0, bytesRead), cancellationToken).ConfigureAwait(false);
}
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
}
}
diff --git a/DisCatSharp.VoiceNext/VoiceApplication.cs b/DisCatSharp.VoiceNext/VoiceApplication.cs
index 034cec8ca..6a09a59e5 100644
--- a/DisCatSharp.VoiceNext/VoiceApplication.cs
+++ b/DisCatSharp.VoiceNext/VoiceApplication.cs
@@ -1,44 +1,44 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.VoiceNext;
/// <summary>
/// Represents encoder settings preset for Opus.
/// </summary>
public enum VoiceApplication : int
{
/// <summary>
/// Defines that the encoder must optimize settings for voice data.
/// </summary>
Voice = 2048,
/// <summary>
/// Defines that the encoder must optimize settings for music data.
/// </summary>
Music = 2049,
/// <summary>
/// Defines that the encoder must optimize settings for low latency applications.
/// </summary>
LowLatency = 2051
}
diff --git a/DisCatSharp.VoiceNext/VoiceNextConfiguration.cs b/DisCatSharp.VoiceNext/VoiceNextConfiguration.cs
index 065119d04..bef7f93b6 100644
--- a/DisCatSharp.VoiceNext/VoiceNextConfiguration.cs
+++ b/DisCatSharp.VoiceNext/VoiceNextConfiguration.cs
@@ -1,65 +1,65 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 Microsoft.Extensions.DependencyInjection;
namespace DisCatSharp.VoiceNext;
/// <summary>
/// VoiceNext client configuration.
/// </summary>
public sealed class VoiceNextConfiguration
{
/// <summary>
/// <para>Sets the audio format for Opus. This will determine the quality of the audio output.</para>
/// <para>Defaults to <see cref="AudioFormat.Default"/>.</para>
/// </summary>
public AudioFormat AudioFormat { internal get; set; } = AudioFormat.Default;
/// <summary>
/// <para>Sets whether incoming voice receiver should be enabled.</para>
/// <para>Defaults to false.</para>
/// </summary>
public bool EnableIncoming { internal get; set; }
/// <summary>
/// <para>Sets the size of the packet queue.</para>
/// <para>Defaults to 25 or ~500ms.</para>
/// </summary>
public int PacketQueueSize { internal get; set; } = 25;
/// <summary>
/// Creates a new instance of <see cref="VoiceNextConfiguration"/>.
/// </summary>
[ActivatorUtilitiesConstructor]
public VoiceNextConfiguration() { }
/// <summary>
/// Creates a new instance of <see cref="VoiceNextConfiguration"/>, copying the properties of another configuration.
/// </summary>
/// <param name="other">Configuration the properties of which are to be copied.</param>
public VoiceNextConfiguration(VoiceNextConfiguration other)
{
this.AudioFormat = new AudioFormat(other.AudioFormat.SampleRate, other.AudioFormat.ChannelCount, other.AudioFormat.VoiceApplication);
this.EnableIncoming = other.EnableIncoming;
}
}
diff --git a/DisCatSharp.VoiceNext/VoiceNextConnection.cs b/DisCatSharp.VoiceNext/VoiceNextConnection.cs
index 624a3340a..a7e029d97 100644
--- a/DisCatSharp.VoiceNext/VoiceNextConnection.cs
+++ b/DisCatSharp.VoiceNext/VoiceNextConnection.cs
@@ -1,1359 +1,1359 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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);
/// <summary>
/// VoiceNext connection to a voice channel.
/// </summary>
public sealed class VoiceNextConnection : IDisposable
{
/// <summary>
/// Triggered whenever a user speaks in the connected voice channel.
/// </summary>
public event AsyncEventHandler<VoiceNextConnection, UserSpeakingEventArgs> UserSpeaking
{
add => this._userSpeaking.Register(value);
remove => this._userSpeaking.Unregister(value);
}
private readonly AsyncEvent<VoiceNextConnection, UserSpeakingEventArgs> _userSpeaking;
/// <summary>
/// Triggered whenever a user joins voice in the connected guild.
/// </summary>
public event AsyncEventHandler<VoiceNextConnection, VoiceUserJoinEventArgs> UserJoined
{
add => this._userJoined.Register(value);
remove => this._userJoined.Unregister(value);
}
private readonly AsyncEvent<VoiceNextConnection, VoiceUserJoinEventArgs> _userJoined;
/// <summary>
/// Triggered whenever a user leaves voice in the connected guild.
/// </summary>
public event AsyncEventHandler<VoiceNextConnection, VoiceUserLeaveEventArgs> UserLeft
{
add => this._userLeft.Register(value);
remove => this._userLeft.Unregister(value);
}
private readonly AsyncEvent<VoiceNextConnection, VoiceUserLeaveEventArgs> _userLeft;
/// <summary>
/// Triggered whenever voice data is received from the connected voice channel.
/// </summary>
public event AsyncEventHandler<VoiceNextConnection, VoiceReceiveEventArgs> VoiceReceived
{
add => this._voiceReceived.Register(value);
remove => this._voiceReceived.Unregister(value);
}
private readonly AsyncEvent<VoiceNextConnection, VoiceReceiveEventArgs> _voiceReceived;
/// <summary>
/// Triggered whenever voice WebSocket throws an exception.
/// </summary>
public event AsyncEventHandler<VoiceNextConnection, SocketErrorEventArgs> VoiceSocketErrored
{
add => this._voiceSocketError.Register(value);
remove => this._voiceSocketError.Unregister(value);
}
private readonly AsyncEvent<VoiceNextConnection, SocketErrorEventArgs> _voiceSocketError;
internal event VoiceDisconnectedEventHandler VoiceDisconnected;
/// <summary>
/// Gets the unix epoch.
/// </summary>
private static DateTimeOffset s_unixEpoch { get; } = new(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
/// <summary>
/// Gets the discord.
/// </summary>
private readonly DiscordClient _discord;
/// <summary>
/// Gets the guild.
/// </summary>
private readonly DiscordGuild _guild;
/// <summary>
/// Gets the transmitting s s r cs.
/// </summary>
private readonly ConcurrentDictionary<uint, AudioSender> _transmittingSsrCs;
/// <summary>
/// Gets the udp client.
/// </summary>
private readonly BaseUdpClient _udpClient;
/// <summary>
/// Gets or sets the voice ws.
/// </summary>
private IWebSocketClient _voiceWs;
/// <summary>
/// Gets or sets the heartbeat task.
/// </summary>
private Task _heartbeatTask;
/// <summary>
/// Gets or sets the heartbeat interval.
/// </summary>
private int _heartbeatInterval;
/// <summary>
/// Gets or sets the last heartbeat.
/// </summary>
private DateTimeOffset _lastHeartbeat;
/// <summary>
/// Gets or sets the token source.
/// </summary>
private CancellationTokenSource _tokenSource;
/// <summary>
/// Gets the token.
/// </summary>
private CancellationToken TOKEN
=> this._tokenSource.Token;
/// <summary>
/// Gets or sets the server data.
/// </summary>
internal VoiceServerUpdatePayload ServerData { get; set; }
/// <summary>
/// Gets or sets the state data.
/// </summary>
internal VoiceStateUpdatePayload StateData { get; set; }
/// <summary>
/// Gets or sets a value indicating whether resume.
/// </summary>
internal bool Resume { get; set; }
/// <summary>
/// Gets the configuration.
/// </summary>
private readonly VoiceNextConfiguration _configuration;
/// <summary>
/// Gets or sets the opus.
/// </summary>
private Opus _opus;
/// <summary>
/// Gets or sets the sodium.
/// </summary>
private Sodium _sodium;
/// <summary>
/// Gets or sets the rtp.
/// </summary>
private Rtp _rtp;
/// <summary>
/// Gets or sets the selected encryption mode.
/// </summary>
private EncryptionMode _selectedEncryptionMode;
/// <summary>
/// Gets or sets the nonce.
/// </summary>
private uint _nonce;
/// <summary>
/// Gets or sets the sequence.
/// </summary>
private ushort _sequence;
/// <summary>
/// Gets or sets the timestamp.
/// </summary>
private uint _timestamp;
/// <summary>
/// Gets or sets the s s r c.
/// </summary>
private uint _ssrc;
/// <summary>
/// Gets or sets the key.
/// </summary>
private byte[] _key;
/// <summary>
/// Gets or sets the discovered endpoint.
/// </summary>
private IpEndpoint _discoveredEndpoint;
/// <summary>
/// Gets or sets the web socket endpoint.
/// </summary>
internal ConnectionEndpoint WebSocketEndpoint { get; set; }
/// <summary>
/// Gets or sets the udp endpoint.
/// </summary>
internal ConnectionEndpoint UdpEndpoint { get; set; }
/// <summary>
/// Gets or sets the ready wait.
/// </summary>
private readonly TaskCompletionSource<bool> _readyWait;
/// <summary>
/// Gets or sets a value indicating whether is initialized.
/// </summary>
private bool _isInitialized;
/// <summary>
/// Gets or sets a value indicating whether is disposed.
/// </summary>
private bool _isDisposed;
/// <summary>
/// Gets or sets the playing wait.
/// </summary>
private TaskCompletionSource<bool> _playingWait;
/// <summary>
/// Gets the pause event.
/// </summary>
private readonly AsyncManualResetEvent _pauseEvent;
/// <summary>
/// Gets or sets the transmit stream.
/// </summary>
private VoiceTransmitSink _transmitStream;
/// <summary>
/// Gets the transmit channel.
/// </summary>
private readonly Channel<RawVoicePacket> _transmitChannel;
/// <summary>
/// Gets the keepalive timestamps.
/// </summary>
private readonly ConcurrentDictionary<ulong, long> _keepaliveTimestamps;
private ulong _lastKeepalive;
/// <summary>
/// Gets or sets the sender task.
/// </summary>
private Task _senderTask;
/// <summary>
/// Gets or sets the sender token source.
/// </summary>
private CancellationTokenSource _senderTokenSource;
/// <summary>
/// Gets the sender token.
/// </summary>
private CancellationToken SENDER_TOKEN
=> this._senderTokenSource.Token;
/// <summary>
/// Gets or sets the receiver task.
/// </summary>
private Task _receiverTask;
/// <summary>
/// Gets or sets the receiver token source.
/// </summary>
private CancellationTokenSource _receiverTokenSource;
/// <summary>
/// Gets the receiver token.
/// </summary>
private CancellationToken RECEIVER_TOKEN
=> this._receiverTokenSource.Token;
/// <summary>
/// Gets or sets the keepalive task.
/// </summary>
private Task _keepaliveTask;
/// <summary>
/// Gets or sets the keepalive token source.
/// </summary>
private CancellationTokenSource _keepaliveTokenSource;
/// <summary>
/// Gets the keepalive token.
/// </summary>
private CancellationToken KEEPALIVE_TOKEN
=> this._keepaliveTokenSource.Token;
private volatile bool _isSpeaking;
/// <summary>
/// Gets the audio format used by the Opus encoder.
/// </summary>
public AudioFormat AudioFormat => this._configuration.AudioFormat;
/// <summary>
/// Gets whether this connection is still playing audio.
/// </summary>
public bool IsPlaying
=> this._playingWait != null && !this._playingWait.Task.IsCompleted;
/// <summary>
/// Gets the websocket round-trip time in ms.
/// </summary>
public int WebSocketPing
=> Volatile.Read(ref this._wsPing);
private int _wsPing;
/// <summary>
/// Gets the UDP round-trip time in ms.
/// </summary>
public int UdpPing
=> Volatile.Read(ref this._udpPing);
private int _udpPing;
private int _queueCount;
/// <summary>
/// Gets the channel this voice client is connected to.
/// </summary>
public DiscordChannel TargetChannel { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="VoiceNextConnection"/> class.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="guild">The guild.</param>
/// <param name="channel">The channel.</param>
/// <param name="config">The config.</param>
/// <param name="server">The server.</param>
/// <param name="state">The state.</param>
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<uint, AudioSender>();
this._userSpeaking = new AsyncEvent<VoiceNextConnection, UserSpeakingEventArgs>("VNEXT_USER_SPEAKING", TimeSpan.Zero, this._discord.EventErrorHandler);
this._userJoined = new AsyncEvent<VoiceNextConnection, VoiceUserJoinEventArgs>("VNEXT_USER_JOINED", TimeSpan.Zero, this._discord.EventErrorHandler);
this._userLeft = new AsyncEvent<VoiceNextConnection, VoiceUserLeaveEventArgs>("VNEXT_USER_LEFT", TimeSpan.Zero, this._discord.EventErrorHandler);
this._voiceReceived = new AsyncEvent<VoiceNextConnection, VoiceReceiveEventArgs>("VNEXT_VOICE_RECEIVED", TimeSpan.Zero, this._discord.EventErrorHandler);
this._voiceSocketError = new AsyncEvent<VoiceNextConnection, SocketErrorEventArgs>("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<bool>();
this._playingWait = null;
this._transmitChannel = Channel.CreateBounded<RawVoicePacket>(new BoundedChannelOptions(this._configuration.PacketQueueSize));
this._keepaliveTimestamps = new ConcurrentDictionary<ulong, long>();
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();
}
/// <summary>
/// Connects to the specified voice channel.
/// </summary>
/// <returns>A task representing the connection operation.</returns>
internal Task ConnectAsync()
{
var gwuri = new UriBuilder
{
Scheme = "wss",
Host = this.WebSocketEndpoint.Hostname,
Query = "encoding=json&v=4"
};
return this._voiceWs.ConnectAsync(gwuri.Uri);
}
/// <summary>
/// Reconnects .
/// </summary>
/// <returns>A Task.</returns>
internal Task ReconnectAsync()
=> this._voiceWs.DisconnectAsync();
/// <summary>
/// Starts .
/// </summary>
/// <returns>A Task.</returns>
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);
}
/// <summary>
/// Waits the for ready async.
/// </summary>
/// <returns>A Task.</returns>
internal Task WaitForReadyAsync()
=> this._readyWait.Task;
/// <summary>
/// Enqueues the packet async.
/// </summary>
/// <param name="packet">The packet.</param>
/// <param name="token">The token.</param>
/// <returns>A Task.</returns>
internal async Task EnqueuePacketAsync(RawVoicePacket packet, CancellationToken token = default)
{
await this._transmitChannel.Writer.WriteAsync(packet, token).ConfigureAwait(false);
this._queueCount++;
}
/// <summary>
/// Prepares the packet.
/// </summary>
/// <param name="pcm">The pcm.</param>
/// <param name="target">The target.</param>
/// <param name="length">The length.</param>
/// <returns>A bool.</returns>
internal bool PreparePacket(ReadOnlySpan<byte> pcm, out byte[] target, out int length)
{
target = null;
length = 0;
if (this._isDisposed)
return false;
var audioFormat = this.AudioFormat;
var packetArray = ArrayPool<byte>.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);
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<byte> nonce = stackalloc byte[Sodium.NonceSize];
switch (this._selectedEncryptionMode)
{
case EncryptionMode.XSalsa20Poly1305:
this._sodium.GenerateNonce(packet[..Rtp.HEADER_SIZE], nonce);
break;
case EncryptionMode.XSalsa20Poly1305Suffix:
this._sodium.GenerateNonce(nonce);
break;
case EncryptionMode.XSalsa20Poly1305Lite:
this._sodium.GenerateNonce(this._nonce++, nonce);
break;
default:
ArrayPool<byte>.Shared.Return(packetArray);
throw new Exception("Unsupported encryption mode.");
}
Span<byte> 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;
}
/// <summary>
/// Voices the sender task.
/// </summary>
/// <returns>A Task.</returns>
private async Task VoiceSenderTask()
{
var token = this.SENDER_TOKEN;
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<bool>();
}
// 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<byte>.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<byte>.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);
}
}
}
/// <summary>
/// Processes the packet.
/// </summary>
/// <param name="data">The data.</param>
/// <param name="opus">The opus.</param>
/// <param name="pcm">The pcm.</param>
/// <param name="pcmPackets">The pcm packets.</param>
/// <param name="voiceSender">The voice sender.</param>
/// <param name="outputFormat">The output format.</param>
/// <returns>A bool.</returns>
private bool ProcessPacket(ReadOnlySpan<byte> data, ref Memory<byte> opus, ref Memory<byte> pcm, IList<ReadOnlyMemory<byte>> pcmPackets, out AudioSender voiceSender, out AudioFormat outputFormat)
{
voiceSender = null;
outputFormat = default;
if (!this._rtp.IsRtpHeader(data))
return false;
this._rtp.DecodeHeader(data, out var shortSequence, out var timestamp, out var ssrc, out var hasExtension);
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;
var sequence = vtx.GetTrueSequenceAfterWrapping(shortSequence);
ushort gap = 0;
if (vtx.LastTrueSequence is ulong lastTrueSequence)
{
if (sequence <= lastTrueSequence) // out-of-order packet; discard
return false;
gap = (ushort)(sequence - 1 - lastTrueSequence);
if (gap >= 5)
this._discord.Logger.LogWarning(VoiceNextEvents.VoiceReceiveFailure, "5 or more voice packets were dropped when receiving");
}
Span<byte> 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.LastTrueSequence = sequence;
}
return true;
}
/// <summary>
/// Processes the voice packet.
/// </summary>
/// <param name="data">The data.</param>
/// <returns>A Task.</returns>
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<ReadOnlyMemory<byte>>();
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,
User = vtx.User,
PcmData = pcmFiller,
OpusData = Array.Empty<byte>().AsMemory(),
AudioFormat = audioFormat,
AudioDuration = audioFormat.CalculateSampleDuration(pcmFiller.Length)
}).ConfigureAwait(false);
await this._voiceReceived.InvokeAsync(this, new VoiceReceiveEventArgs(this._discord.ServiceProvider)
{
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");
}
}
/// <summary>
/// Processes the keepalive.
/// </summary>
/// <param name="data">The data.</param>
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");
}
}
/// <summary>
/// Udps the receiver task.
/// </summary>
/// <returns>A Task.</returns>
private async Task UdpReceiverTask()
{
var token = this.RECEIVER_TOKEN;
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);
}
}
/// <summary>
/// Sends a speaking status to the connected voice channel.
/// </summary>
/// <param name="speaking">Whether the current user is speaking or not.</param>
/// <returns>A task representing the sending operation.</returns>
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);
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="sampleDuration">Duration, in ms, to use for audio packets.</param>
/// <returns>Transmit stream.</returns>
public VoiceTransmitSink GetTransmitSink(int sampleDuration = 20)
{
if (!AudioFormat.AllowedSampleDurations.Contains(sampleDuration))
throw new ArgumentOutOfRangeException(nameof(sampleDuration), "Invalid PCM sample duration specified.");
this._transmitStream ??= new VoiceTransmitSink(this, sampleDuration);
return this._transmitStream;
}
/// <summary>
/// Asynchronously waits for playback to be finished. Playback is finished when speaking = false is signaled.
/// </summary>
/// <returns>A task representing the waiting operation.</returns>
public async Task WaitForPlaybackFinishAsync()
{
if (this._playingWait != null)
await this._playingWait.Task.ConfigureAwait(false);
}
/// <summary>
/// Pauses playback.
/// </summary>
public void Pause()
=> this._pauseEvent.Reset();
/// <summary>
/// Asynchronously resumes playback.
/// </summary>
/// <returns></returns>
public async Task ResumeAsync()
=> await this._pauseEvent.SetAsync().ConfigureAwait(false);
/// <summary>
/// Disconnects and disposes this voice connection.
/// </summary>
public void Disconnect()
=> this.Dispose();
/// <summary>
/// Disconnects and disposes this voice connection.
/// </summary>
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);
GC.SuppressFinalize(this);
}
/// <summary>
/// Heartbeats .
/// </summary>
/// <returns>A Task.</returns>
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;
}
}
}
/// <summary>
/// Keepalives .
/// </summary>
/// <returns>A Task.</returns>
private async Task KeepaliveAsync()
{
await Task.Yield();
var token = this.KEEPALIVE_TOKEN;
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);
}
}
/// <summary>
/// Stage1S .
/// </summary>
/// <param name="voiceReady">The voice ready.</param>
/// <returns>A Task.</returns>
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 discovery finished - discovered endpoint is {0}:{1}", ip, port);
void PreparePacket(byte[] packet)
{
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.SENDER_TOKEN);
this._receiverTokenSource = new CancellationTokenSource();
this._receiverTask = Task.Run(this.UdpReceiverTask, this.RECEIVER_TOKEN);
}
/// <summary>
/// Stage2S .
/// </summary>
/// <param name="voiceSessionDescription">The voice session description.</param>
/// <returns>A Task.</returns>
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);
}
/// <summary>
/// Handles the dispatch.
/// </summary>
/// <param name="jo">The jo.</param>
/// <returns>A Task.</returns>
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<VoiceReadyPayload>();
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<VoiceSessionDescriptionPayload>();
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<VoiceSpeakingPayload>();
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,
User = resolvedUser,
};
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)
{
User = await this._discord.GetUserAsync(spd.UserId.Value).ConfigureAwait(false)
};
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<int>();
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<VoiceUserJoinPayload>();
var usrj = await this._discord.GetUserAsync(ujpd.UserId).ConfigureAwait(false);
{
var opus = this._opus.CreateDecoder();
var vtx = new AudioSender(ujpd.Ssrc, opus)
{
User = usrj
};
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);
break;
case 13: // CLIENT_DISCONNECTED
this._discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received CLIENT_DISCONNECTED (OP13)");
var ulpd = opp.ToObject<VoiceUserLeavePayload>();
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._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
}).ConfigureAwait(false);
break;
default:
this._discord.Logger.LogTrace(VoiceNextEvents.VoiceDispatch, "Received unknown voice opcode (OP{0})", opc);
break;
}
}
/// <summary>
/// Voices the w s_ socket closed.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The e.</param>
/// <returns>A Task.</returns>
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);
}
}
/// <summary>
/// Voices the w s_ socket message.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The e.</param>
/// <returns>A Task.</returns>
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));
}
/// <summary>
/// Voices the w s_ socket opened.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The e.</param>
/// <returns>A Task.</returns>
private Task VoiceWS_SocketOpened(IWebSocketClient client, SocketEventArgs e)
=> this.StartAsync();
/// <summary>
/// Voices the ws_ socket exception.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The e.</param>
/// <returns>A Task.</returns>
private Task VoiceWs_SocketException(IWebSocketClient client, SocketErrorEventArgs e)
=> this._voiceSocketError.InvokeAsync(this, new SocketErrorEventArgs(this._discord.ServiceProvider) { Exception = e.Exception });
/// <summary>
/// Ws the send async.
/// </summary>
/// <param name="payload">The payload.</param>
/// <returns>A Task.</returns>
private async Task WsSendAsync(string payload)
{
this._discord.Logger.LogTrace(VoiceNextEvents.VoiceWsTx, payload);
await this._voiceWs.SendMessageAsync(payload).ConfigureAwait(false);
}
/// <summary>
/// Gets the unix timestamp.
/// </summary>
/// <param name="dt">The datetime.</param>
private static uint UnixTimestamp(DateTime dt)
{
var ts = dt - s_unixEpoch;
var sd = ts.TotalSeconds;
var si = (uint)sd;
return si;
}
}
diff --git a/DisCatSharp.VoiceNext/VoiceNextEvents.cs b/DisCatSharp.VoiceNext/VoiceNextEvents.cs
index 940859a53..df5b6fff8 100644
--- a/DisCatSharp.VoiceNext/VoiceNextEvents.cs
+++ b/DisCatSharp.VoiceNext/VoiceNextEvents.cs
@@ -1,81 +1,81 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 Microsoft.Extensions.Logging;
namespace DisCatSharp.VoiceNext;
/// <summary>
/// Contains well-defined event IDs used by the VoiceNext extension.
/// </summary>
public static class VoiceNextEvents
{
/// <summary>
/// Miscellaneous events, that do not fit in any other category.
/// </summary>
public static EventId Misc { get; } = new(300, "VoiceNext");
/// <summary>
/// Events pertaining to Voice Gateway connection lifespan, specifically, heartbeats.
/// </summary>
public static EventId VoiceHeartbeat { get; } = new(301, nameof(VoiceHeartbeat));
/// <summary>
/// Events pertaining to Voice Gateway connection early lifespan, specifically, the establishing thereof as well as negotiating various modes.
/// </summary>
public static EventId VoiceHandshake { get; } = new(302, nameof(VoiceHandshake));
/// <summary>
/// Events emitted when incoming voice data is corrupted, or packets are being dropped.
/// </summary>
public static EventId VoiceReceiveFailure { get; } = new(303, nameof(VoiceReceiveFailure));
/// <summary>
/// Events pertaining to UDP connection lifespan, specifically the keepalive (or heartbeats).
/// </summary>
public static EventId VoiceKeepalive { get; } = new(304, nameof(VoiceKeepalive));
/// <summary>
/// Events emitted for high-level dispatch receive events.
/// </summary>
public static EventId VoiceDispatch { get; } = new(305, nameof(VoiceDispatch));
/// <summary>
/// Events emitted for Voice Gateway connection closes, clean or otherwise.
/// </summary>
public static EventId VoiceConnectionClose { get; } = new(306, nameof(VoiceConnectionClose));
/// <summary>
/// Events emitted when decoding data received via Voice Gateway fails for any reason.
/// </summary>
public static EventId VoiceGatewayError { get; } = new(307, nameof(VoiceGatewayError));
/// <summary>
/// Events containing raw (but decompressed) payloads, received from Discord Voice Gateway.
/// </summary>
public static EventId VoiceWsRx { get; } = new(308, "Voice ↓");
/// <summary>
/// Events containing raw payloads, as they're being sent to Discord Voice Gateway.
/// </summary>
public static EventId VoiceWsTx { get; } = new(309, "Voice ↑");
}
diff --git a/DisCatSharp.VoiceNext/VoiceNextExtension.cs b/DisCatSharp.VoiceNext/VoiceNextExtension.cs
index 3ab318245..7a6ae249a 100644
--- a/DisCatSharp.VoiceNext/VoiceNextExtension.cs
+++ b/DisCatSharp.VoiceNext/VoiceNextExtension.cs
@@ -1,265 +1,265 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Tasks;
using DisCatSharp.Entities;
using DisCatSharp.Enums;
using DisCatSharp.EventArgs;
using DisCatSharp.Net;
using DisCatSharp.VoiceNext.Entities;
using Newtonsoft.Json;
namespace DisCatSharp.VoiceNext;
/// <summary>
/// Represents VoiceNext extension, which acts as Discord voice client.
/// </summary>
public sealed class VoiceNextExtension : BaseExtension
{
/// <summary>
/// Gets or sets the configuration.
/// </summary>
private readonly VoiceNextConfiguration _configuration;
/// <summary>
/// Gets or sets the active connections.
/// </summary>
private readonly ConcurrentDictionary<ulong, VoiceNextConnection> _activeConnections;
/// <summary>
/// Gets or sets the voice state updates.
/// </summary>
private readonly ConcurrentDictionary<ulong, TaskCompletionSource<VoiceStateUpdateEventArgs>> _voiceStateUpdates;
/// <summary>
/// Gets or sets the voice server updates.
/// </summary>
private readonly ConcurrentDictionary<ulong, TaskCompletionSource<VoiceServerUpdateEventArgs>> _voiceServerUpdates;
/// <summary>
/// Gets whether this connection has incoming voice enabled.
/// </summary>
public bool IsIncomingEnabled { get; }
/// <summary>
/// Initializes a new instance of the <see cref="VoiceNextExtension"/> class.
/// </summary>
/// <param name="config">The config.</param>
internal VoiceNextExtension(VoiceNextConfiguration config)
{
this._configuration = new VoiceNextConfiguration(config);
this.IsIncomingEnabled = config.EnableIncoming;
this._activeConnections = new ConcurrentDictionary<ulong, VoiceNextConnection>();
this._voiceStateUpdates = new ConcurrentDictionary<ulong, TaskCompletionSource<VoiceStateUpdateEventArgs>>();
this._voiceServerUpdates = new ConcurrentDictionary<ulong, TaskCompletionSource<VoiceServerUpdateEventArgs>>();
}
/// <summary>
/// DO NOT USE THIS MANUALLY.
/// </summary>
/// <param name="client">DO NOT USE THIS MANUALLY.</param>
/// <exception cref="InvalidOperationException"/>
protected internal override void Setup(DiscordClient client)
{
if (this.Client != null)
throw new InvalidOperationException("What did I tell you?");
this.Client = client;
this.Client.VoiceStateUpdated += this.Client_VoiceStateUpdate;
this.Client.VoiceServerUpdated += this.Client_VoiceServerUpdate;
}
/// <summary>
/// Create a VoiceNext connection for the specified channel.
/// </summary>
/// <param name="channel">Channel to connect to.</param>
/// <returns>VoiceNext connection for this channel.</returns>
public async Task<VoiceNextConnection> ConnectAsync(DiscordChannel channel)
{
if (channel.Type != ChannelType.Voice && channel.Type != ChannelType.Stage)
throw new ArgumentException("Invalid channel specified; needs to be voice or stage channel", nameof(channel));
if (channel.Guild == null)
throw new ArgumentException("Invalid channel specified; needs to be guild channel", nameof(channel));
if (!channel.PermissionsFor(channel.Guild.CurrentMember).HasPermission(Permissions.AccessChannels | Permissions.UseVoice))
throw new InvalidOperationException("You need AccessChannels and UseVoice permission to connect to this voice channel");
var gld = channel.Guild;
if (this._activeConnections.ContainsKey(gld.Id))
throw new InvalidOperationException("This guild already has a voice connection");
var vstut = new TaskCompletionSource<VoiceStateUpdateEventArgs>();
var vsrut = new TaskCompletionSource<VoiceServerUpdateEventArgs>();
this._voiceStateUpdates[gld.Id] = vstut;
this._voiceServerUpdates[gld.Id] = vsrut;
var vsd = new VoiceDispatch
{
OpCode = 4,
Payload = new VoiceStateUpdatePayload
{
GuildId = gld.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 vstup = new VoiceStateUpdatePayload
{
SessionId = vstu.SessionId,
UserId = vstu.User.Id
};
var vsru = await vsrut.Task.ConfigureAwait(false);
var vsrup = new VoiceServerUpdatePayload
{
Endpoint = vsru.Endpoint,
GuildId = vsru.Guild.Id,
Token = vsru.VoiceToken
};
var vnc = new VoiceNextConnection(this.Client, gld, channel, this._configuration, vsrup, vstup);
vnc.VoiceDisconnected += this.Vnc_VoiceDisconnected;
await vnc.ConnectAsync().ConfigureAwait(false);
await vnc.WaitForReadyAsync().ConfigureAwait(false);
this._activeConnections[gld.Id] = vnc;
return vnc;
}
/// <summary>
/// Gets a VoiceNext connection for specified guild.
/// </summary>
/// <param name="guild">Guild to get VoiceNext connection for.</param>
/// <returns>VoiceNext connection for the specified guild.</returns>
public VoiceNextConnection GetConnection(DiscordGuild guild) => this._activeConnections.ContainsKey(guild.Id) ? this._activeConnections[guild.Id] : null;
/// <summary>
/// Vnc_S the voice disconnected.
/// </summary>
/// <param name="guild">The guild.</param>
/// <returns>A Task.</returns>
private async Task Vnc_VoiceDisconnected(DiscordGuild guild)
{
VoiceNextConnection vnc = null;
if (this._activeConnections.ContainsKey(guild.Id))
this._activeConnections.TryRemove(guild.Id, out vnc);
var vsd = new VoiceDispatch
{
OpCode = 4,
Payload = new VoiceStateUpdatePayload
{
GuildId = guild.Id,
ChannelId = null
}
};
var vsj = JsonConvert.SerializeObject(vsd, Formatting.None);
await (guild.Discord as DiscordClient).WsSendAsync(vsj).ConfigureAwait(false);
}
/// <summary>
/// Client_S the voice state update.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The e.</param>
/// <returns>A Task.</returns>
private Task Client_VoiceStateUpdate(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.Client.CurrentUser.Id)
{
if (e.After.Channel == null && this._activeConnections.TryRemove(gld.Id, out var ac))
ac.Disconnect();
if (this._activeConnections.TryGetValue(e.Guild.Id, out var vnc))
vnc.TargetChannel = e.Channel;
if (!string.IsNullOrWhiteSpace(e.SessionId) && e.Channel != null && this._voiceStateUpdates.TryRemove(gld.Id, out var xe))
xe.SetResult(e);
}
return Task.CompletedTask;
}
/// <summary>
/// Client_S the voice server update.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The e.</param>
/// <returns>A Task.</returns>
private async Task Client_VoiceServerUpdate(DiscordClient client, VoiceServerUpdateEventArgs e)
{
var gld = e.Guild;
if (gld == null)
return;
if (this._activeConnections.TryGetValue(e.Guild.Id, out var vnc))
{
vnc.ServerData = new VoiceServerUpdatePayload
{
Endpoint = e.Endpoint,
GuildId = e.Guild.Id,
Token = e.VoiceToken
};
var eps = e.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;
}
vnc.WebSocketEndpoint = new ConnectionEndpoint { Hostname = eph, Port = epp };
vnc.Resume = false;
await vnc.ReconnectAsync().ConfigureAwait(false);
}
if (this._voiceServerUpdates.ContainsKey(gld.Id))
{
this._voiceServerUpdates.TryRemove(gld.Id, out var xe);
xe.SetResult(e);
}
}
}
diff --git a/DisCatSharp.VoiceNext/VoiceTransmitSink.cs b/DisCatSharp.VoiceNext/VoiceTransmitSink.cs
index 867a29f5c..049f2275c 100644
--- a/DisCatSharp.VoiceNext/VoiceTransmitSink.cs
+++ b/DisCatSharp.VoiceNext/VoiceTransmitSink.cs
@@ -1,282 +1,282 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using DisCatSharp.VoiceNext.Codec;
namespace DisCatSharp.VoiceNext;
/// <summary>
/// Sink used to transmit audio data via <see cref="VoiceNextConnection"/>.
/// </summary>
public sealed class VoiceTransmitSink : IDisposable
{
/// <summary>
/// Gets the PCM sample duration for this sink.
/// </summary>
public int SampleDuration { get; }
/// <summary>
/// Gets the length of the PCM buffer for this sink.
/// Written packets should adhere to this size, but the sink will adapt to fit.
/// </summary>
public int SampleLength
=> this._pcmBuffer.Length;
/// <summary>
/// Gets or sets the volume modifier for this sink. Changing this will alter the volume of the output. 1.0 is 100%.
/// </summary>
public double VolumeModifier
{
get => this._volume;
set
{
if (value < 0 || value > 2.5)
throw new ArgumentOutOfRangeException(nameof(value), "Volume needs to be between 0% and 250%.");
this._volume = value;
}
}
private double _volume = 1.0;
/// <summary>
/// Gets the connection.
/// </summary>
private readonly VoiceNextConnection _connection;
/// <summary>
/// Gets the pcm buffer.
/// </summary>
private readonly byte[] _pcmBuffer;
/// <summary>
/// Gets the pcm memory.
/// </summary>
private readonly Memory<byte> _pcmMemory;
/// <summary>
/// Gets or sets the pcm buffer length.
/// </summary>
private int _pcmBufferLength;
/// <summary>
/// Gets the write semaphore.
/// </summary>
private readonly SemaphoreSlim _writeSemaphore;
/// <summary>
/// Gets the filters.
/// </summary>
private readonly List<IVoiceFilter> _filters;
/// <summary>
/// Initializes a new instance of the <see cref="VoiceTransmitSink"/> class.
/// </summary>
/// <param name="vnc">The vnc.</param>
/// <param name="pcmBufferDuration">The pcm buffer duration.</param>
internal VoiceTransmitSink(VoiceNextConnection vnc, int pcmBufferDuration)
{
this._connection = vnc;
this.SampleDuration = pcmBufferDuration;
this._pcmBuffer = new byte[vnc.AudioFormat.CalculateSampleSize(pcmBufferDuration)];
this._pcmMemory = this._pcmBuffer.AsMemory();
this._pcmBufferLength = 0;
this._writeSemaphore = new SemaphoreSlim(1, 1);
this._filters = new List<IVoiceFilter>();
}
/// <summary>
/// Writes PCM data to the sink. The data is prepared for transmission, and enqueued.
/// </summary>
/// <param name="buffer">PCM data buffer to send.</param>
/// <param name="offset">Start of the data in the buffer.</param>
/// <param name="count">Number of bytes from the buffer.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default) => await this.WriteAsync(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken).ConfigureAwait(false);
/// <summary>
/// Writes PCM data to the sink. The data is prepared for transmission, and enqueued.
/// </summary>
/// <param name="buffer">PCM data buffer to send.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
public async Task WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
{
await this._writeSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
var remaining = buffer.Length;
var buffSpan = buffer;
var pcmSpan = this._pcmMemory;
while (remaining > 0)
{
var len = Math.Min(pcmSpan.Length - this._pcmBufferLength, remaining);
var tgt = pcmSpan[this._pcmBufferLength..];
var src = buffSpan[..len];
src.CopyTo(tgt);
this._pcmBufferLength += len;
remaining -= len;
buffSpan = buffSpan[len..];
if (this._pcmBufferLength == this._pcmBuffer.Length)
{
this.ApplyFiltersSync(pcmSpan);
this._pcmBufferLength = 0;
var packet = ArrayPool<byte>.Shared.Rent(this._pcmMemory.Length);
var packetMemory = packet.AsMemory()[..this._pcmMemory.Length];
this._pcmMemory.CopyTo(packetMemory);
await this._connection.EnqueuePacketAsync(new RawVoicePacket(packetMemory, this.SampleDuration, false, packet), cancellationToken).ConfigureAwait(false);
}
}
}
finally
{
this._writeSemaphore.Release();
}
}
/// <summary>
/// Flushes the rest of the PCM data in this buffer to VoiceNext packet queue.
/// </summary>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
public async Task FlushAsync(CancellationToken cancellationToken = default)
{
var pcm = this._pcmMemory;
Helpers.ZeroFill(pcm[this._pcmBufferLength..].Span);
this.ApplyFiltersSync(pcm);
var packet = ArrayPool<byte>.Shared.Rent(pcm.Length);
var packetMemory = packet.AsMemory()[..pcm.Length];
pcm.CopyTo(packetMemory);
await this._connection.EnqueuePacketAsync(new RawVoicePacket(packetMemory, this.SampleDuration, false, packet), cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Pauses playback.
/// </summary>
public void Pause()
=> this._connection.Pause();
/// <summary>
/// Resumes playback.
/// </summary>
/// <returns></returns>
public async Task ResumeAsync()
=> await this._connection.ResumeAsync().ConfigureAwait(false);
/// <summary>
/// Gets the collection of installed PCM filters, in order of their execution.
/// </summary>
/// <returns>Installed PCM filters, in order of execution.</returns>
public IEnumerable<IVoiceFilter> GetInstalledFilters()
{
foreach (var filter in this._filters)
yield return filter;
}
/// <summary>
/// Installs a new PCM filter, with specified execution order.
/// </summary>
/// <param name="filter">Filter to install.</param>
/// <param name="order">Order of the new filter. This determines where the filter will be inserted in the filter pipeline.</param>
public void InstallFilter(IVoiceFilter filter, int order = int.MaxValue)
{
if (filter == null)
throw new ArgumentNullException(nameof(filter));
if (order < 0)
throw new ArgumentOutOfRangeException(nameof(order), "Filter order must be greater than or equal to 0.");
lock (this._filters)
{
var filters = this._filters;
if (order >= filters.Count)
filters.Add(filter);
else
filters.Insert(order, filter);
}
}
/// <summary>
/// Uninstalls an installed PCM filter.
/// </summary>
/// <param name="filter">Filter to uninstall.</param>
/// <returns>Whether the filter was uninstalled.</returns>
public bool UninstallFilter(IVoiceFilter filter)
{
if (filter == null)
throw new ArgumentNullException(nameof(filter));
lock (this._filters)
{
var filters = this._filters;
return filters.Contains(filter) && filters.Remove(filter);
}
}
/// <summary>
/// Applies the filters sync.
/// </summary>
/// <param name="pcmSpan">The pcm span.</param>
private void ApplyFiltersSync(Memory<byte> pcmSpan)
{
var pcm16 = MemoryMarshal.Cast<byte, short>(pcmSpan.Span);
// pass through any filters, if applicable
lock (this._filters)
{
if (this._filters.Any())
{
foreach (var filter in this._filters)
filter.Transform(pcm16, this._connection.AudioFormat, this.SampleDuration);
}
}
if (this.VolumeModifier != 1)
{
// alter volume
for (var i = 0; i < pcm16.Length; i++)
pcm16[i] = (short)(pcm16[i] * this.VolumeModifier);
}
}
/// <summary>
/// Disposes .
/// </summary>
public void Dispose()
=> this._writeSemaphore?.Dispose();
}
diff --git a/DisCatSharp/AsyncManualResetEvent.cs b/DisCatSharp/AsyncManualResetEvent.cs
index a45a88ccd..0f7665d14 100644
--- a/DisCatSharp/AsyncManualResetEvent.cs
+++ b/DisCatSharp/AsyncManualResetEvent.cs
@@ -1,83 +1,83 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Threading;
using System.Threading.Tasks;
namespace DisCatSharp;
// source: https://blogs.msdn.microsoft.com/pfxteam/2012/02/11/building-async-coordination-primitives-part-1-asyncmanualresetevent/
/// <summary>
/// Implements an async version of a <see cref="ManualResetEvent"/>
/// This class does currently not support Timeouts or the use of CancellationTokens
/// </summary>
internal class AsyncManualResetEvent
{
/// <summary>
/// Gets a value indicating whether this is set.
/// </summary>
public bool IsSet => this._tsc != null && this._tsc.Task.IsCompleted;
private TaskCompletionSource<bool> _tsc;
/// <summary>
/// Initializes a new instance of the <see cref="AsyncManualResetEvent"/> class.
/// </summary>
public AsyncManualResetEvent()
: this(false)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="AsyncManualResetEvent"/> class.
/// </summary>
/// <param name="initialState">If true, initial state.</param>
public AsyncManualResetEvent(bool initialState)
{
this._tsc = new TaskCompletionSource<bool>();
if (initialState) this._tsc.TrySetResult(true);
}
/// <summary>
/// Waits the async waiter.
/// </summary>
public Task WaitAsync() => this._tsc.Task;
/// <summary>
/// Sets the async task.
/// </summary>
public Task SetAsync() => Task.Run(() => this._tsc.TrySetResult(true));
/// <summary>
/// Resets the async waiter.
/// </summary>
public void Reset()
{
while (true)
{
var tsc = this._tsc;
if (!tsc.Task.IsCompleted || Interlocked.CompareExchange(ref this._tsc, new TaskCompletionSource<bool>(), tsc) == tsc)
return;
}
}
}
diff --git a/DisCatSharp/BaseExtension.cs b/DisCatSharp/BaseExtension.cs
index 2199924bb..2d7035a83 100644
--- a/DisCatSharp/BaseExtension.cs
+++ b/DisCatSharp/BaseExtension.cs
@@ -1,40 +1,40 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents base for all DisCatSharp extensions. To implement your own extension, extend this class, and implement its abstract members.
/// </summary>
public abstract class BaseExtension
{
/// <summary>
/// Gets the instance of <see cref="DiscordClient"/> this extension is attached to.
/// </summary>
public DiscordClient Client { get; protected set; }
/// <summary>
/// Initializes this extension for given <see cref="DiscordClient"/> instance.
/// </summary>
/// <param name="client">Discord client to initialize for.</param>
protected internal abstract void Setup(DiscordClient client);
}
diff --git a/DisCatSharp/Clients/BaseDiscordClient.cs b/DisCatSharp/Clients/BaseDiscordClient.cs
index 1a3ced1d4..d918f8c97 100644
--- a/DisCatSharp/Clients/BaseDiscordClient.cs
+++ b/DisCatSharp/Clients/BaseDiscordClient.cs
@@ -1,326 +1,326 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a common base for various Discord Client implementations.
/// </summary>
public abstract class BaseDiscordClient : IDisposable
{
/// <summary>
/// Gets the api client.
/// </summary>
internal protected DiscordApiClient ApiClient { get; }
/// <summary>
/// Gets the configuration.
/// </summary>
internal protected DiscordConfiguration Configuration { get; }
/// <summary>
/// Gets the instance of the logger for this client.
/// </summary>
public ILogger<BaseDiscordClient> Logger { get; internal set; }
/// <summary>
/// Gets the string representing the version of bot lib.
/// </summary>
public string VersionString { get; }
/// <summary>
/// Gets the bot library name.
/// </summary>
public string BotLibrary { get; }
[Obsolete("Use GetLibraryDeveloperTeamAsync")]
public DisCatSharpTeam LibraryDeveloperTeamAsync
=> this.GetLibraryDevelopmentTeamAsync().Result;
/// <summary>
/// Gets the current user.
/// </summary>
public DiscordUser CurrentUser { get; internal set; }
/// <summary>
/// Gets the current application.
/// </summary>
public DiscordApplication CurrentApplication { get; internal set; }
/// <summary>
/// Exposes a <see cref="HttpClient">Http Client</see> for custom operations.
/// </summary>
public HttpClient RestClient { get; internal set; }
/// <summary>
/// Gets the cached guilds for this client.
/// </summary>
public abstract IReadOnlyDictionary<ulong, DiscordGuild> Guilds { get; }
/// <summary>
/// Gets the cached users for this client.
/// </summary>
public ConcurrentDictionary<ulong, DiscordUser> UserCache { get; internal set; }
/// <summary>
/// <para>Gets the service provider.</para>
/// <para>This allows passing data around without resorting to static members.</para>
/// <para>Defaults to null.</para>
/// </summary>
internal IServiceProvider ServiceProvider { get; set; }
/// <summary>
/// Gets the list of available voice regions. Note that this property will not contain VIP voice regions.
/// </summary>
public IReadOnlyDictionary<string, DiscordVoiceRegion> VoiceRegions
=> this.VoiceRegionsLazy.Value;
/// <summary>
/// Gets the list of available voice regions. This property is meant as a way to modify <see cref="VoiceRegions"/>.
/// </summary>
protected internal ConcurrentDictionary<string, DiscordVoiceRegion> InternalVoiceRegions { get; set; }
internal Lazy<IReadOnlyDictionary<string, DiscordVoiceRegion>> VoiceRegionsLazy;
/// <summary>
/// Initializes this Discord API client.
/// </summary>
/// <param name="config">Configuration for this client.</param>
protected BaseDiscordClient(DiscordConfiguration config)
{
this.Configuration = new DiscordConfiguration(config);
this.ServiceProvider = config.ServiceProvider;
if (this.ServiceProvider != null)
{
this.Configuration.LoggerFactory ??= config.ServiceProvider.GetService<ILoggerFactory>();
this.Logger = config.ServiceProvider.GetService<ILogger<BaseDiscordClient>>();
}
if (this.Configuration.LoggerFactory == null)
{
this.Configuration.LoggerFactory = new DefaultLoggerFactory();
this.Configuration.LoggerFactory.AddProvider(new DefaultLoggerProvider(this));
}
this.Logger ??= this.Configuration.LoggerFactory.CreateLogger<BaseDiscordClient>();
this.ApiClient = new DiscordApiClient(this);
this.UserCache = new ConcurrentDictionary<ulong, DiscordUser>();
this.InternalVoiceRegions = new ConcurrentDictionary<string, DiscordVoiceRegion>();
this.VoiceRegionsLazy = new Lazy<IReadOnlyDictionary<string, DiscordVoiceRegion>>(() => new ReadOnlyDictionary<string, DiscordVoiceRegion>(this.InternalVoiceRegions));
this.RestClient = new();
this.RestClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", Utilities.GetUserAgent());
this.RestClient.DefaultRequestHeaders.TryAddWithoutValidation("X-Discord-Locale", this.Configuration.Locale);
var a = typeof(DiscordClient).GetTypeInfo().Assembly;
var iv = a.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
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";
}
/// <summary>
/// Gets the current API application.
/// </summary>
public async Task<DiscordApplication> 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<string>(tapp.RpcOrigins) : null,
Flags = tapp.Flags,
RequiresCodeGrant = tapp.BotRequiresCodeGrant,
IsPublic = tapp.IsPublicBot,
IsHook = tapp.IsHook,
Type = tapp.Type,
PrivacyPolicyUrl = tapp.PrivacyPolicyUrl,
TermsOfServiceUrl = tapp.TermsOfServiceUrl,
CustomInstallUrl = tapp.CustomInstallUrl,
InstallParams = tapp.InstallParams,
RoleConnectionsVerificationUrl = tapp.RoleConnectionsVerificationUrl,
Tags = (tapp.Tags ?? Enumerable.Empty<string>()).ToArray()
};
if (tapp.Team == null)
{
app.Owners = new List<DiscordUser>(new[] { new DiscordUser(tapp.Owner) });
app.Team = null;
app.TeamName = null;
}
else
{
app.Team = new DiscordTeam(tapp.Team);
var members = tapp.Team.Members
.Select(x => new DiscordTeamMember(x) { TeamId = app.Team.Id, TeamName = app.Team.Name, User = new DiscordUser(x.User) })
.ToArray();
var owners = members
.Where(x => x.MembershipStatus == DiscordTeamMembershipStatus.Accepted)
.Select(x => x.User)
.ToArray();
app.Owners = new List<DiscordUser>(owners);
app.Team.Owner = owners.FirstOrDefault(x => x.Id == tapp.Team.OwnerId);
app.Team.Members = new List<DiscordTeamMember>(members);
app.TeamName = app.Team.Name;
}
app.GuildId = tapp.GuildId.ValueOrDefault();
app.Slug = tapp.Slug.ValueOrDefault();
app.PrimarySkuId = tapp.PrimarySkuId.ValueOrDefault();
app.VerifyKey = tapp.VerifyKey.ValueOrDefault();
app.CoverImageHash = tapp.CoverImageHash.ValueOrDefault();
return app;
}
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Gets a list of voice regions.
/// </summary>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<IReadOnlyList<DiscordVoiceRegion>> ListVoiceRegionsAsync()
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> this.ApiClient.ListVoiceRegionsAsync();
/// <summary>
/// Initializes this client. This method fetches information about current user, application, and voice regions.
/// </summary>
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.IsEmpty)
{
var vrs = await this.ListVoiceRegionsAsync().ConfigureAwait(false);
foreach (var xvr in vrs)
this.InternalVoiceRegions.TryAdd(xvr.Id, xvr);
}
}
/// <summary>
/// Gets the current gateway info for the provided token.
/// <para>If no value is provided, the configuration value will be used instead.</para>
/// </summary>
/// <returns>A gateway info object.</returns>
public async Task<GatewayInfo> 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);
}
/// <summary>
/// Gets some information about the development team behind DisCatSharp.
/// Can be used for crediting etc.
/// <para>Note: This call contacts servers managed by the DCS team, no information is collected.</para>
/// <returns>The team, or null with errors being logged on failure.</returns>
/// </summary>
[Obsolete("Don't use this right now, inactive")]
public async Task<DisCatSharpTeam> GetLibraryDevelopmentTeamAsync()
=> await DisCatSharpTeam.Get(this.RestClient, this.Logger, this.ApiClient).ConfigureAwait(false);
/// <summary>
/// Gets a cached user.
/// </summary>
/// <param name="userId">The user id.</param>
internal DiscordUser GetCachedOrEmptyUserInternal(ulong userId)
{
this.TryGetCachedUserInternal(userId, out var user);
return user;
}
/// <summary>
/// Tries the get a cached user.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="user">The user.</param>
internal bool TryGetCachedUserInternal(ulong userId, out DiscordUser user)
{
if (this.UserCache.TryGetValue(userId, out user))
return true;
user = new DiscordUser { Id = userId, Discord = this };
return false;
}
/// <summary>
/// Disposes this client.
/// </summary>
public abstract void Dispose();
}
diff --git a/DisCatSharp/Clients/DiscordClient.Dispatch.cs b/DisCatSharp/Clients/DiscordClient.Dispatch.cs
index 435e0e93b..1e5de97f1 100644
--- a/DisCatSharp/Clients/DiscordClient.Dispatch.cs
+++ b/DisCatSharp/Clients/DiscordClient.Dispatch.cs
@@ -1,3563 +1,3567 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
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;
/// <summary>
/// Represents a discord Logger.ent.L
/// </summary>
public sealed partial class DiscordClient
{
#region Private Fields
private string _resumeGatewayUrl;
private string _sessionId;
private bool _guildDownloadCompleted;
private readonly Dictionary<string, KeyValuePair<TimeoutHandler, Timer>> _tempTimers = new();
/// <summary>
/// Represents a timeout handler.
/// </summary>
internal class TimeoutHandler
{
/// <summary>
/// Gets the member.
/// </summary>
internal readonly DiscordMember Member;
/// <summary>
/// Gets the guild.
/// </summary>
internal readonly DiscordGuild Guild;
/// <summary>
/// Gets the old timeout value.
/// </summary>
internal DateTime? TimeoutUntilOld;
/// <summary>
/// Gets the new timeout value.
/// </summary>
internal DateTime? TimeoutUntilNew;
/// <summary>
/// Constructs a new <see cref="TimeoutHandler"/>.
/// </summary>
/// <param name="mbr">The affected member.</param>
/// <param name="guild">The affected guild.</param>
/// <param name="too">The old timeout value.</param>
/// <param name="ton">The new timeout value.</param>
internal TimeoutHandler(DiscordMember mbr, DiscordGuild guild, DateTime? too, DateTime? ton)
{
this.Guild = guild;
this.Member = mbr;
this.TimeoutUntilOld = too;
this.TimeoutUntilNew = ton;
}
}
#endregion
#region Dispatch Handler
/// <summary>
/// Handles the dispatch payloads.
/// </summary>
/// <param name="payload">The payload.</param>
internal async Task HandleDispatchAsync(GatewayPayload payload)
{
if (payload.Data is not JObject dat)
{
this.Logger.LogWarning(LoggerEvents.WebSocketReceive, "Invalid payload body (this message is probably safe to ignore); opcode: {0} event: {1}; payload: {2}", payload.OpCode, payload.EventName, payload.Data);
return;
}
await this._payloadReceived.InvokeAsync(this, new PayloadReceivedEventArgs(this.ServiceProvider)
{
EventName = payload.EventName,
PayloadObject = dat
}).ConfigureAwait(false);
#region Default objects
DiscordChannel chn;
ulong gid;
ulong cid;
ulong uid;
DiscordStageInstance stg = default;
DiscordIntegration itg = default;
DiscordThreadChannel trd = default;
DiscordThreadChannelMember trdm = default;
DiscordScheduledEvent gse = default;
TransportUser usr = default;
TransportMember mbr = default;
TransportUser refUsr = default;
TransportMember refMbr = default;
JToken rawMbr = default;
var rawRefMsg = dat["referenced_message"];
#endregion
switch (payload.EventName.ToLowerInvariant())
{
#region Gateway Status
case "ready":
var glds = (JArray)dat["guilds"];
await this.OnReadyEventAsync(dat.ToObject<ReadyPayload>(), glds).ConfigureAwait(false);
break;
case "resumed":
await this.OnResumedAsync().ConfigureAwait(false);
break;
#endregion
#region Channel
case "channel_create":
chn = dat.ToObject<DiscordChannel>();
await this.OnChannelCreateEventAsync(chn).ConfigureAwait(false);
break;
case "channel_update":
await this.OnChannelUpdateEventAsync(dat.ToObject<DiscordChannel>()).ConfigureAwait(false);
break;
case "channel_delete":
chn = dat.ToObject<DiscordChannel>();
await this.OnChannelDeleteEventAsync(chn.IsPrivate ? dat.ToObject<DiscordDmChannel>() : 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<DiscordGuild>(), (JArray)dat["members"], dat["presences"].ToDiscordObject<IEnumerable<DiscordPresence>>()).ConfigureAwait(false);
break;
case "guild_update":
await this.OnGuildUpdateEventAsync(dat.ToDiscordObject<DiscordGuild>(), (JArray)dat["members"]).ConfigureAwait(false);
break;
case "guild_delete":
await this.OnGuildDeleteEventAsync(dat.ToDiscordObject<DiscordGuild>()).ConfigureAwait(false);
break;
case "guild_audit_log_entry_create":
gid = (ulong)dat["guild_id"];
dat.Remove("guild_id");
await this.OnGuildAuditLogEntryCreateEventAsync(this.GuildsInternal[gid], dat).ConfigureAwait(false);
break;
case "guild_sync":
gid = (ulong)dat["id"];
await this.OnGuildSyncEventAsync(this.GuildsInternal[gid], (bool)dat["large"], (JArray)dat["members"], dat["presences"].ToDiscordObject<IEnumerable<DiscordPresence>>()).ConfigureAwait(false);
break;
case "guild_emojis_update":
gid = (ulong)dat["guild_id"];
var ems = dat["emojis"].ToObject<IEnumerable<DiscordEmoji>>();
await this.OnGuildEmojisUpdateEventAsync(this.GuildsInternal[gid], ems).ConfigureAwait(false);
break;
case "guild_stickers_update":
gid = (ulong)dat["guild_id"];
var strs = dat["stickers"].ToDiscordObject<IEnumerable<DiscordSticker>>();
await this.OnStickersUpdatedAsync(strs, gid).ConfigureAwait(false);
break;
case "guild_integrations_update":
gid = (ulong)dat["guild_id"];
// discord fires this event inconsistently if the current user leaves a guild.
if (!this.GuildsInternal.ContainsKey(gid))
return;
await this.OnGuildIntegrationsUpdateEventAsync(this.GuildsInternal[gid]).ConfigureAwait(false);
break;
- /*
- case "guild_join_request_create":
- break;
+ /*
+ case "guild_join_request_create":
+ break;
- case "guild_join_request_update":
- break;
+ case "guild_join_request_update":
+ break;
- case "guild_join_request_delete":
- break;
- */
+ case "guild_join_request_delete":
+ break;
+ */
#endregion
#region Guild Automod
case "auto_moderation_rule_create":
await this.OnAutomodRuleCreated(dat.ToDiscordObject<AutomodRule>());
break;
case "auto_moderation_rule_update":
await this.OnAutomodRuleUpdated(dat.ToDiscordObject<AutomodRule>());
break;
case "auto_moderation_rule_delete":
await this.OnAutomodRuleDeleted(dat.ToDiscordObject<AutomodRule>());
break;
case "auto_moderation_action_execution":
gid = (ulong)dat["guild_id"];
await this.OnAutomodActionExecuted(this.GuildsInternal[gid], dat);
break;
#endregion
#region Guild Ban
case "guild_ban_add":
usr = dat["user"].ToObject<TransportUser>();
gid = (ulong)dat["guild_id"];
await this.OnGuildBanAddEventAsync(usr, this.GuildsInternal[gid]).ConfigureAwait(false);
break;
case "guild_ban_remove":
usr = dat["user"].ToObject<TransportUser>();
gid = (ulong)dat["guild_id"];
await this.OnGuildBanRemoveEventAsync(usr, this.GuildsInternal[gid]).ConfigureAwait(false);
break;
#endregion
#region Guild Event
case "guild_scheduled_event_create":
gse = dat.ToObject<DiscordScheduledEvent>();
gid = (ulong)dat["guild_id"];
await this.OnGuildScheduledEventCreateEventAsync(gse, this.GuildsInternal[gid]).ConfigureAwait(false);
break;
case "guild_scheduled_event_update":
gse = dat.ToObject<DiscordScheduledEvent>();
gid = (ulong)dat["guild_id"];
await this.OnGuildScheduledEventUpdateEventAsync(gse, this.GuildsInternal[gid]).ConfigureAwait(false);
break;
case "guild_scheduled_event_delete":
gse = dat.ToObject<DiscordScheduledEvent>();
gid = (ulong)dat["guild_id"];
await this.OnGuildScheduledEventDeleteEventAsync(gse, this.GuildsInternal[gid]).ConfigureAwait(false);
break;
case "guild_scheduled_event_user_add":
gid = (ulong)dat["guild_id"];
uid = (ulong)dat["user_id"];
await this.OnGuildScheduledEventUserAddedEventAsync((ulong)dat["guild_scheduled_event_id"], uid, this.GuildsInternal[gid]).ConfigureAwait(false);
break;
case "guild_scheduled_event_user_remove":
gid = (ulong)dat["guild_id"];
uid = (ulong)dat["user_id"];
await this.OnGuildScheduledEventUserRemovedEventAsync((ulong)dat["guild_scheduled_event_id"], uid, this.GuildsInternal[gid]).ConfigureAwait(false);
break;
#endregion
#region Guild Integration
case "integration_create":
gid = (ulong)dat["guild_id"];
itg = dat.ToObject<DiscordIntegration>();
// discord fires this event inconsistently if the current user leaves a guild.
if (!this.GuildsInternal.ContainsKey(gid))
return;
await this.OnGuildIntegrationCreateEventAsync(this.GuildsInternal[gid], itg).ConfigureAwait(false);
break;
case "integration_update":
gid = (ulong)dat["guild_id"];
itg = dat.ToObject<DiscordIntegration>();
// discord fires this event inconsistently if the current user leaves a guild.
if (!this.GuildsInternal.ContainsKey(gid))
return;
await this.OnGuildIntegrationUpdateEventAsync(this.GuildsInternal[gid], itg).ConfigureAwait(false);
break;
case "integration_delete":
gid = (ulong)dat["guild_id"];
// discord fires this event inconsistently if the current user leaves a guild.
if (!this.GuildsInternal.ContainsKey(gid))
return;
await this.OnGuildIntegrationDeleteEventAsync(this.GuildsInternal[gid], (ulong)dat["id"], (ulong?)dat["application_id"]).ConfigureAwait(false);
break;
#endregion
#region Guild Member
case "guild_member_add":
gid = (ulong)dat["guild_id"];
await this.OnGuildMemberAddEventAsync(dat.ToObject<TransportMember>(), this.GuildsInternal[gid]).ConfigureAwait(false);
break;
case "guild_member_remove":
gid = (ulong)dat["guild_id"];
usr = dat["user"].ToObject<TransportUser>();
if (!this.GuildsInternal.ContainsKey(gid))
{
// discord fires this event inconsistently if the current user leaves a guild.
if (usr.Id != this.CurrentUser.Id)
this.Logger.LogError(LoggerEvents.WebSocketReceive, "Could not find {0} in guild cache", gid);
return;
}
await this.OnGuildMemberRemoveEventAsync(usr, this.GuildsInternal[gid]).ConfigureAwait(false);
break;
case "guild_member_update":
gid = (ulong)dat["guild_id"];
await this.OnGuildMemberUpdateEventAsync(dat.ToDiscordObject<TransportMember>(), this.GuildsInternal[gid], dat["roles"].ToObject<IEnumerable<ulong>>(), (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<DiscordRole>(), this.GuildsInternal[gid]).ConfigureAwait(false);
break;
case "guild_role_update":
gid = (ulong)dat["guild_id"];
await this.OnGuildRoleUpdateEventAsync(dat["role"].ToObject<DiscordRole>(), this.GuildsInternal[gid]).ConfigureAwait(false);
break;
case "guild_role_delete":
gid = (ulong)dat["guild_id"];
await this.OnGuildRoleDeleteEventAsync((ulong)dat["role_id"], this.GuildsInternal[gid]).ConfigureAwait(false);
break;
#endregion
#region Invite
case "invite_create":
gid = (ulong)dat["guild_id"];
cid = (ulong)dat["channel_id"];
await this.OnInviteCreateEventAsync(cid, gid, dat.ToObject<DiscordInvite>()).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<TransportMember>();
if (rawRefMsg != null && rawRefMsg.HasValues)
{
if (rawRefMsg.SelectToken("author") != null)
{
refUsr = rawRefMsg.SelectToken("author").ToObject<TransportUser>();
}
if (rawRefMsg.SelectToken("member") != null)
{
refMbr = rawRefMsg.SelectToken("member").ToObject<TransportMember>();
}
}
await this.OnMessageCreateEventAsync(dat.ToDiscordObject<DiscordMessage>(), dat["author"].ToObject<TransportUser>(), mbr, refUsr, refMbr).ConfigureAwait(false);
break;
case "message_update":
rawMbr = dat["member"];
if (rawMbr != null)
mbr = rawMbr.ToObject<TransportMember>();
if (rawRefMsg != null && rawRefMsg.HasValues)
{
if (rawRefMsg.SelectToken("author") != null)
{
refUsr = rawRefMsg.SelectToken("author").ToObject<TransportUser>();
}
if (rawRefMsg.SelectToken("member") != null)
{
refMbr = rawRefMsg.SelectToken("member").ToObject<TransportMember>();
}
}
await this.OnMessageUpdateEventAsync(dat.ToDiscordObject<DiscordMessage>(), dat["author"]?.ToObject<TransportUser>(), 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[]>(), (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<TransportMember>();
// TODO: Add burst stuff
await this.OnMessageReactionAddAsync((ulong)dat["user_id"], (ulong)dat["message_id"], (ulong)dat["channel_id"], (ulong?)dat["guild_id"], mbr, dat["emoji"].ToObject<DiscordEmoji>(), (bool)dat["burst"]).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<DiscordEmoji>(), (bool)dat["burst"]).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<DiscordStageInstance>();
await this.OnStageInstanceCreateEventAsync(stg).ConfigureAwait(false);
break;
case "stage_instance_update":
stg = dat.ToObject<DiscordStageInstance>();
await this.OnStageInstanceUpdateEventAsync(stg).ConfigureAwait(false);
break;
case "stage_instance_delete":
stg = dat.ToObject<DiscordStageInstance>();
await this.OnStageInstanceDeleteEventAsync(stg).ConfigureAwait(false);
break;
#endregion
#region Thread
case "thread_create":
trd = dat.ToObject<DiscordThreadChannel>();
await this.OnThreadCreateEventAsync(trd).ConfigureAwait(false);
break;
case "thread_update":
trd = dat.ToObject<DiscordThreadChannel>();
await this.OnThreadUpdateEventAsync(trd).ConfigureAwait(false);
break;
case "thread_delete":
trd = dat.ToObject<DiscordThreadChannel>();
await this.OnThreadDeleteEventAsync(trd).ConfigureAwait(false);
break;
case "thread_list_sync":
gid = (ulong)dat["guild_id"]; //get guild
await this.OnThreadListSyncEventAsync(this.GuildsInternal[gid], dat["channel_ids"].ToObject<IReadOnlyList<ulong?>>(), dat["threads"].ToObject<IReadOnlyList<DiscordThreadChannel>>(), dat["members"].ToObject<IReadOnlyList<DiscordThreadChannelMember>>()).ConfigureAwait(false);
break;
case "thread_member_update":
trdm = dat.ToObject<DiscordThreadChannelMember>();
await this.OnThreadMemberUpdateEventAsync(trdm).ConfigureAwait(false);
break;
case "thread_members_update":
gid = (ulong)dat["guild_id"];
await this.OnThreadMembersUpdateEventAsync(this.GuildsInternal[gid], (ulong)dat["id"], (JArray)dat["added_members"], (JArray)dat["removed_member_ids"], (int)dat["member_count"]).ConfigureAwait(false);
break;
#endregion
#region Activities
case "embedded_activity_update":
gid = (ulong)dat["guild_id"];
cid = (ulong)dat["channel_id"];
await this.OnEmbeddedActivityUpdateAsync((JObject)dat["embedded_activity"], this.GuildsInternal[gid], cid, (JArray)dat["users"], (ulong)dat["embedded_activity"]["application_id"]).ConfigureAwait(false);
break;
#endregion
#region User/Presence Update
case "presence_update":
await this.OnPresenceUpdateEventAsync(dat, (JObject)dat["user"]).ConfigureAwait(false);
break;
case "user_settings_update":
await this.OnUserSettingsUpdateEventAsync(dat.ToObject<TransportUser>()).ConfigureAwait(false);
break;
case "user_update":
await this.OnUserUpdateEventAsync(dat.ToObject<TransportUser>()).ConfigureAwait(false);
break;
#endregion
#region Voice
case "voice_state_update":
await this.OnVoiceStateUpdateEventAsync(dat).ConfigureAwait(false);
break;
case "voice_server_update":
gid = (ulong)dat["guild_id"];
await this.OnVoiceServerUpdateEventAsync((string)dat["endpoint"], (string)dat["token"], this.GuildsInternal[gid]).ConfigureAwait(false);
break;
#endregion
#region Interaction/Integration/Application
case "interaction_create":
rawMbr = dat["member"];
if (rawMbr != null)
{
mbr = dat["member"].ToObject<TransportMember>();
usr = mbr.User;
}
else
{
usr = dat["user"].ToObject<TransportUser>();
}
cid = (ulong)dat["channel_id"];
// Console.WriteLine(dat.ToString()); // Get raw interaction payload.
await this.OnInteractionCreateAsync((ulong?)dat["guild_id"], cid, usr, mbr, dat.ToDiscordObject<DiscordInteraction>(), dat.ToString()).ConfigureAwait(false);
break;
case "application_command_create":
await this.OnApplicationCommandCreateAsync(dat.ToObject<DiscordApplicationCommand>(), (ulong?)dat["guild_id"]).ConfigureAwait(false);
break;
case "application_command_update":
await this.OnApplicationCommandUpdateAsync(dat.ToObject<DiscordApplicationCommand>(), (ulong?)dat["guild_id"]).ConfigureAwait(false);
break;
case "application_command_delete":
await this.OnApplicationCommandDeleteAsync(dat.ToObject<DiscordApplicationCommand>(), (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 "guild_application_command_index_update":
// TODO: Implement.
break;
case "application_command_permissions_update":
var aid = (ulong)dat["application_id"];
if (aid != this.CurrentApplication.Id)
return;
var pms = dat["permissions"].ToObject<IEnumerable<DiscordApplicationCommandPermission>>();
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<TransportMember>();
await this.OnTypingStartEventAsync((ulong)dat["user_id"], cid, this.InternalGetCachedChannel(cid), (ulong?)dat["guild_id"], Utilities.GetDateTimeOffset((long)dat["timestamp"]), mbr).ConfigureAwait(false);
break;
case "webhooks_update":
gid = (ulong)dat["guild_id"];
cid = (ulong)dat["channel_id"];
await this.OnWebhooksUpdateAsync(this.GuildsInternal[gid].GetChannel(cid), this.GuildsInternal[gid]).ConfigureAwait(false);
break;
default:
await this.OnUnknownEventAsync(payload).ConfigureAwait(false);
this.Logger.LogWarning(LoggerEvents.WebSocketReceive, "Unknown event: {0}\npayload: {1}", payload.EventName, dat.ToString(Newtonsoft.Json.Formatting.Indented));
break;
#endregion
}
}
#endregion
#region Events
#region Gateway
/// <summary>
/// Handles the ready event.
/// </summary>
/// <param name="ready">The ready payload.</param>
/// <param name="rawGuilds">The raw guilds.</param>
internal async Task OnReadyEventAsync(ReadyPayload ready, JArray rawGuilds)
{
//ready.CurrentUser.Discord = this;
var rusr = ready.CurrentUser;
this.CurrentUser.Username = rusr.Username;
this.CurrentUser.Discriminator = rusr.Discriminator;
this.CurrentUser.AvatarHash = rusr.AvatarHash;
this.CurrentUser.MfaEnabled = rusr.MfaEnabled;
this.CurrentUser.Verified = rusr.Verified;
this.CurrentUser.IsBot = rusr.IsBot;
this.CurrentUser.Flags = rusr.Flags;
this.GatewayVersion = ready.GatewayVersion;
this._sessionId = ready.SessionId;
this._resumeGatewayUrl = ready.ResumeGatewayUrl;
var rawGuildIndex = rawGuilds.ToDictionary(xt => (ulong)xt["id"], xt => (JObject)xt);
this.GuildsInternal.Clear();
foreach (var guild in ready.Guilds)
{
guild.Discord = this;
guild.ChannelsInternal ??= new ConcurrentDictionary<ulong, DiscordChannel>();
foreach (var xc in guild.Channels.Values)
{
xc.GuildId = guild.Id;
xc.Initialize(this);
}
guild.RolesInternal ??= new ConcurrentDictionary<ulong, DiscordRole>();
foreach (var xr in guild.Roles.Values)
{
xr.Discord = this;
xr.GuildId = guild.Id;
}
var rawGuild = rawGuildIndex[guild.Id];
var rawMembers = (JArray)rawGuild["members"];
if (guild.MembersInternal != null)
guild.MembersInternal.Clear();
else
guild.MembersInternal = new ConcurrentDictionary<ulong, DiscordMember>();
if (rawMembers != null)
{
foreach (var xj in rawMembers)
{
var xtm = xj.ToObject<TransportMember>();
var xu = new DiscordUser(xtm.User) { Discord = this };
xu = this.UserCache.AddOrUpdate(xtm.User.Id, xu, (id, old) =>
{
old.Username = xu.Username;
old.Discriminator = xu.Discriminator;
old.AvatarHash = xu.AvatarHash;
return old;
});
guild.MembersInternal[xtm.User.Id] = new DiscordMember(xtm) { Discord = this, GuildId = guild.Id };
}
}
guild.EmojisInternal ??= new ConcurrentDictionary<ulong, DiscordEmoji>();
foreach (var xe in guild.Emojis.Values)
xe.Discord = this;
guild.StickersInternal ??= new ConcurrentDictionary<ulong, DiscordSticker>();
foreach (var xs in guild.Stickers.Values)
xs.Discord = this;
guild.VoiceStatesInternal ??= new ConcurrentDictionary<ulong, DiscordVoiceState>();
foreach (var xvs in guild.VoiceStates.Values)
xvs.Discord = this;
guild.ThreadsInternal ??= new ConcurrentDictionary<ulong, DiscordThreadChannel>();
foreach (var xt in guild.ThreadsInternal.Values)
xt.Discord = this;
guild.StageInstancesInternal ??= new ConcurrentDictionary<ulong, DiscordStageInstance>();
foreach (var xsi in guild.StageInstancesInternal.Values)
xsi.Discord = this;
guild.ScheduledEventsInternal ??= new ConcurrentDictionary<ulong, DiscordScheduledEvent>();
foreach (var xse in guild.ScheduledEventsInternal.Values)
xse.Discord = this;
this.GuildsInternal[guild.Id] = guild;
}
await this._ready.InvokeAsync(this, new ReadyEventArgs(this.ServiceProvider)).ConfigureAwait(false);
}
/// <summary>
/// Handles the resumed event.
/// </summary>
internal Task OnResumedAsync()
{
this.Logger.LogInformation(LoggerEvents.SessionUpdate, "Session resumed");
return this._resumed.InvokeAsync(this, new ReadyEventArgs(this.ServiceProvider));
}
#endregion
#region Channel
/// <summary>
/// Handles the channel create event.
/// </summary>
/// <param name="channel">The channel.</param>
internal async Task OnChannelCreateEventAsync(DiscordChannel channel)
{
channel.Initialize(this);
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);
}
/// <summary>
/// Handles the channel update event.
/// </summary>
/// <param name="channel">The channel.</param>
internal async Task OnChannelUpdateEventAsync(DiscordChannel channel)
{
if (channel == null)
return;
channel.Discord = this;
var gld = channel.Guild;
var channelNew = this.InternalGetCachedChannel(channel.Id);
DiscordChannel channelOld = null;
if (channelNew != null)
{
channelOld = new DiscordChannel
{
Bitrate = channelNew.Bitrate,
Discord = this,
GuildId = channelNew.GuildId,
Id = channelNew.Id,
LastMessageId = channelNew.LastMessageId,
Name = channelNew.Name,
PermissionOverwritesInternal = new List<DiscordOverwrite>(channelNew.PermissionOverwritesInternal),
Position = channelNew.Position,
Topic = channelNew.Topic,
Type = channelNew.Type,
UserLimit = channelNew.UserLimit,
ParentId = channelNew.ParentId,
IsNsfw = channelNew.IsNsfw,
PerUserRateLimit = channelNew.PerUserRateLimit,
RtcRegionId = channelNew.RtcRegionId,
QualityMode = channelNew.QualityMode,
DefaultAutoArchiveDuration = channelNew.DefaultAutoArchiveDuration,
};
channelNew.Bitrate = channel.Bitrate;
channelNew.Name = channel.Name;
channelNew.Position = channel.Position;
channelNew.Topic = channel.Topic;
channelNew.UserLimit = channel.UserLimit;
channelNew.ParentId = channel.ParentId;
channelNew.IsNsfw = channel.IsNsfw;
channelNew.PerUserRateLimit = channel.PerUserRateLimit;
channelNew.Type = channel.Type;
channelNew.RtcRegionId = channel.RtcRegionId;
channelNew.QualityMode = channel.QualityMode;
channelNew.DefaultAutoArchiveDuration = channel.DefaultAutoArchiveDuration;
channelNew.PermissionOverwritesInternal.Clear();
channel.Initialize(this);
channelNew.PermissionOverwritesInternal.AddRange(channel.PermissionOverwritesInternal);
if (channel.Type == ChannelType.Forum)
{
channelOld.PostCreateUserRateLimit = channelNew.PostCreateUserRateLimit;
channelOld.InternalAvailableTags = channelNew.InternalAvailableTags;
channelOld.Template = channelNew.Template;
channelOld.DefaultReactionEmoji = channelNew.DefaultReactionEmoji;
channelOld.DefaultSortOrder = channelNew.DefaultSortOrder;
channelNew.PostCreateUserRateLimit = channel.PostCreateUserRateLimit;
channelNew.Template = channel.Template;
channelNew.DefaultReactionEmoji = channel.DefaultReactionEmoji;
channelNew.DefaultSortOrder = channel.DefaultSortOrder;
if (channelNew.InternalAvailableTags != null && channelNew.InternalAvailableTags.Any())
channelNew.InternalAvailableTags.Clear();
if (channel.InternalAvailableTags != null && channel.InternalAvailableTags.Any())
channelNew.InternalAvailableTags.AddRange(channel.InternalAvailableTags);
}
else
{
channelOld.PostCreateUserRateLimit = null;
channelOld.InternalAvailableTags = null;
channelOld.Template = null;
channelOld.DefaultReactionEmoji = null;
channelOld.DefaultSortOrder = null;
channelNew.PostCreateUserRateLimit = null;
channelNew.InternalAvailableTags = null;
channelNew.Template = null;
channelNew.DefaultReactionEmoji = null;
channelNew.DefaultSortOrder = null;
}
channelOld.Initialize(this);
channelNew.Initialize(this);
if (this.Configuration.AutoRefreshChannelCache && gld != null)
{
await this.RefreshChannelsAsync(channel.Guild.Id);
}
}
else if (gld != null)
{
gld.ChannelsInternal[channel.Id] = channel;
if (this.Configuration.AutoRefreshChannelCache)
{
await this.RefreshChannelsAsync(channel.Guild.Id);
}
}
await this._channelUpdated.InvokeAsync(this, new ChannelUpdateEventArgs(this.ServiceProvider) { ChannelAfter = channelNew, Guild = gld, ChannelBefore = channelOld }).ConfigureAwait(false);
}
/// <summary>
/// Handles the channel delete event.
/// </summary>
/// <param name="channel">The channel.</param>
internal async Task OnChannelDeleteEventAsync(DiscordChannel channel)
{
if (channel == null)
return;
channel.Discord = this;
//if (channel.IsPrivate)
if (channel.Type == ChannelType.Group || channel.Type == ChannelType.Private)
{
var dmChannel = channel as DiscordDmChannel;
await this._dmChannelDeleted.InvokeAsync(this, new DmChannelDeleteEventArgs(this.ServiceProvider) { Channel = dmChannel }).ConfigureAwait(false);
}
else
{
var gld = channel.Guild;
if (gld.ChannelsInternal.TryRemove(channel.Id, out var cachedChannel)) channel = cachedChannel;
if (this.Configuration.AutoRefreshChannelCache)
{
await this.RefreshChannelsAsync(channel.Guild.Id);
}
await this._channelDeleted.InvokeAsync(this, new ChannelDeleteEventArgs(this.ServiceProvider) { Channel = channel, Guild = gld }).ConfigureAwait(false);
}
}
/// <summary>
/// Refreshes the channels.
/// </summary>
/// <param name="guildId">The guild id.</param>
internal async Task RefreshChannelsAsync(ulong guildId)
{
var guild = this.InternalGetCachedGuild(guildId);
var channels = await this.ApiClient.GetGuildChannelsAsync(guildId);
guild.ChannelsInternal.Clear();
foreach (var channel in channels.ToList())
{
channel.Initialize(this);
guild.ChannelsInternal[channel.Id] = channel;
}
}
/// <summary>
/// Handles the channel pins update event.
/// </summary>
/// <param name="guildId">The optional guild id.</param>
/// <param name="channelId">The channel id.</param>
/// <param name="lastPinTimestamp">The optional last pin timestamp.</param>
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
/// <summary>
/// Handles the guild create event.
/// </summary>
/// <param name="guild">The guild.</param>
/// <param name="rawMembers">The raw members.</param>
/// <param name="presences">The presences.</param>
internal async Task OnGuildCreateEventAsync(DiscordGuild guild, JArray rawMembers, IEnumerable<DiscordPresence> 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 = xp.RawActivities
.Select(x => new DiscordActivity(x)).ToArray();
}
this.PresencesInternal[xp.InternalUser.Id] = xp;
}
}
var exists = this.GuildsInternal.TryGetValue(guild.Id, out var foundGuild);
guild.Discord = this;
guild.IsUnavailable = false;
var eventGuild = guild;
if (exists)
guild = foundGuild;
guild.ChannelsInternal ??= new ConcurrentDictionary<ulong, DiscordChannel>();
guild.ThreadsInternal ??= new ConcurrentDictionary<ulong, DiscordThreadChannel>();
guild.RolesInternal ??= new ConcurrentDictionary<ulong, DiscordRole>();
guild.ThreadsInternal ??= new ConcurrentDictionary<ulong, DiscordThreadChannel>();
guild.StickersInternal ??= new ConcurrentDictionary<ulong, DiscordSticker>();
guild.EmojisInternal ??= new ConcurrentDictionary<ulong, DiscordEmoji>();
guild.VoiceStatesInternal ??= new ConcurrentDictionary<ulong, DiscordVoiceState>();
guild.MembersInternal ??= new ConcurrentDictionary<ulong, DiscordMember>();
guild.ScheduledEventsInternal ??= new ConcurrentDictionary<ulong, DiscordScheduledEvent>();
this.UpdateCachedGuild(eventGuild, rawMembers);
guild.JoinedAt = eventGuild.JoinedAt;
guild.IsLarge = eventGuild.IsLarge;
guild.MemberCount = Math.Max(eventGuild.MemberCount, guild.MembersInternal.Count);
guild.IsUnavailable = eventGuild.IsUnavailable;
guild.PremiumSubscriptionCount = eventGuild.PremiumSubscriptionCount;
guild.PremiumTier = eventGuild.PremiumTier;
guild.BannerHash = eventGuild.BannerHash;
guild.VanityUrlCode = eventGuild.VanityUrlCode;
guild.Description = eventGuild.Description;
guild.IsNsfw = eventGuild.IsNsfw;
foreach (var kvp in eventGuild.VoiceStatesInternal) guild.VoiceStatesInternal[kvp.Key] = kvp.Value;
foreach (var kvp in eventGuild.ChannelsInternal) guild.ChannelsInternal[kvp.Key] = kvp.Value;
foreach (var kvp in eventGuild.RolesInternal) guild.RolesInternal[kvp.Key] = kvp.Value;
foreach (var kvp in eventGuild.EmojisInternal) guild.EmojisInternal[kvp.Key] = kvp.Value;
foreach (var kvp in eventGuild.ThreadsInternal) guild.ThreadsInternal[kvp.Key] = kvp.Value;
foreach (var kvp in eventGuild.StickersInternal) guild.StickersInternal[kvp.Key] = kvp.Value;
foreach (var kvp in eventGuild.StageInstancesInternal) guild.StageInstancesInternal[kvp.Key] = kvp.Value;
foreach (var kvp in eventGuild.ScheduledEventsInternal) guild.ScheduledEventsInternal[kvp.Key] = kvp.Value;
foreach (var xc in guild.ChannelsInternal.Values)
{
xc.GuildId = guild.Id;
xc.Initialize(this);
}
foreach (var xt in guild.ThreadsInternal.Values)
{
xt.GuildId = guild.Id;
xt.Discord = this;
}
foreach (var xe in guild.EmojisInternal.Values)
xe.Discord = this;
foreach (var xs in guild.StickersInternal.Values)
xs.Discord = this;
foreach (var xvs in guild.VoiceStatesInternal.Values)
xvs.Discord = this;
foreach (var xsi in guild.StageInstancesInternal.Values)
{
xsi.Discord = this;
xsi.GuildId = guild.Id;
}
foreach (var xr in guild.RolesInternal.Values)
{
xr.Discord = this;
xr.GuildId = guild.Id;
}
foreach (var xse in guild.ScheduledEventsInternal.Values)
{
xse.Discord = this;
xse.GuildId = guild.Id;
if (xse.Creator != null)
xse.Creator.Discord = this;
}
var old = Volatile.Read(ref this._guildDownloadCompleted);
var dcompl = this.GuildsInternal.Values.All(xg => !xg.IsUnavailable);
Volatile.Write(ref this._guildDownloadCompleted, dcompl);
if (exists)
await this._guildAvailable.InvokeAsync(this, new GuildCreateEventArgs(this.ServiceProvider) { Guild = guild }).ConfigureAwait(false);
else
await this._guildCreated.InvokeAsync(this, new GuildCreateEventArgs(this.ServiceProvider) { Guild = guild }).ConfigureAwait(false);
if (dcompl && !old)
await this._guildDownloadCompletedEv.InvokeAsync(this, new GuildDownloadCompletedEventArgs(this.Guilds, this.ServiceProvider)).ConfigureAwait(false);
}
/// <summary>
/// Handles the guild update event.
/// </summary>
/// <param name="guild">The guild.</param>
/// <param name="rawMembers">The raw members.</param>
internal async Task OnGuildUpdateEventAsync(DiscordGuild guild, JArray rawMembers)
{
DiscordGuild oldGuild;
if (!this.GuildsInternal.ContainsKey(guild.Id))
{
this.GuildsInternal[guild.Id] = guild;
oldGuild = null;
}
else
{
var gld = this.GuildsInternal[guild.Id];
oldGuild = new DiscordGuild
{
Discord = gld.Discord,
Name = gld.Name,
AfkChannelId = gld.AfkChannelId,
AfkTimeout = gld.AfkTimeout,
ApplicationId = gld.ApplicationId,
DefaultMessageNotifications = gld.DefaultMessageNotifications,
ExplicitContentFilter = gld.ExplicitContentFilter,
RawFeatures = gld.RawFeatures,
IconHash = gld.IconHash,
Id = gld.Id,
IsLarge = gld.IsLarge,
IsSynced = gld.IsSynced,
IsUnavailable = gld.IsUnavailable,
JoinedAt = gld.JoinedAt,
MemberCount = gld.MemberCount,
MaxMembers = gld.MaxMembers,
MaxPresences = gld.MaxPresences,
ApproximateMemberCount = gld.ApproximateMemberCount,
ApproximatePresenceCount = gld.ApproximatePresenceCount,
MaxVideoChannelUsers = gld.MaxVideoChannelUsers,
DiscoverySplashHash = gld.DiscoverySplashHash,
PreferredLocale = gld.PreferredLocale,
MfaLevel = gld.MfaLevel,
OwnerId = gld.OwnerId,
SplashHash = gld.SplashHash,
SystemChannelId = gld.SystemChannelId,
SystemChannelFlags = gld.SystemChannelFlags,
Description = gld.Description,
WidgetEnabled = gld.WidgetEnabled,
WidgetChannelId = gld.WidgetChannelId,
VerificationLevel = gld.VerificationLevel,
RulesChannelId = gld.RulesChannelId,
PublicUpdatesChannelId = gld.PublicUpdatesChannelId,
VoiceRegionId = gld.VoiceRegionId,
IsNsfw = gld.IsNsfw,
PremiumProgressBarEnabled = gld.PremiumProgressBarEnabled,
PremiumSubscriptionCount = gld.PremiumSubscriptionCount,
PremiumTier = gld.PremiumTier,
ChannelsInternal = new ConcurrentDictionary<ulong, DiscordChannel>(),
ThreadsInternal = new ConcurrentDictionary<ulong, DiscordThreadChannel>(),
EmojisInternal = new ConcurrentDictionary<ulong, DiscordEmoji>(),
StickersInternal = new ConcurrentDictionary<ulong, DiscordSticker>(),
MembersInternal = new ConcurrentDictionary<ulong, DiscordMember>(),
RolesInternal = new ConcurrentDictionary<ulong, DiscordRole>(),
StageInstancesInternal = new ConcurrentDictionary<ulong, DiscordStageInstance>(),
VoiceStatesInternal = new ConcurrentDictionary<ulong, DiscordVoiceState>(),
ScheduledEventsInternal = new ConcurrentDictionary<ulong, DiscordScheduledEvent>()
};
foreach (var kvp in gld.ChannelsInternal) oldGuild.ChannelsInternal[kvp.Key] = kvp.Value;
foreach (var kvp in gld.ThreadsInternal) oldGuild.ThreadsInternal[kvp.Key] = kvp.Value;
foreach (var kvp in gld.EmojisInternal) oldGuild.EmojisInternal[kvp.Key] = kvp.Value;
foreach (var kvp in gld.StickersInternal) oldGuild.StickersInternal[kvp.Key] = kvp.Value;
foreach (var kvp in gld.RolesInternal) oldGuild.RolesInternal[kvp.Key] = kvp.Value;
foreach (var kvp in gld.VoiceStatesInternal) oldGuild.VoiceStatesInternal[kvp.Key] = kvp.Value;
foreach (var kvp in gld.MembersInternal) oldGuild.MembersInternal[kvp.Key] = kvp.Value;
foreach (var kvp in gld.StageInstancesInternal) oldGuild.StageInstancesInternal[kvp.Key] = kvp.Value;
foreach (var kvp in gld.ScheduledEventsInternal) oldGuild.ScheduledEventsInternal[kvp.Key] = kvp.Value;
}
guild.Discord = this;
guild.IsUnavailable = false;
var eventGuild = guild;
guild = this.GuildsInternal[eventGuild.Id];
guild.ChannelsInternal ??= new ConcurrentDictionary<ulong, DiscordChannel>();
guild.ThreadsInternal ??= new ConcurrentDictionary<ulong, DiscordThreadChannel>();
guild.RolesInternal ??= new ConcurrentDictionary<ulong, DiscordRole>();
guild.EmojisInternal ??= new ConcurrentDictionary<ulong, DiscordEmoji>();
guild.StickersInternal ??= new ConcurrentDictionary<ulong, DiscordSticker>();
guild.VoiceStatesInternal ??= new ConcurrentDictionary<ulong, DiscordVoiceState>();
guild.StageInstancesInternal ??= new ConcurrentDictionary<ulong, DiscordStageInstance>();
guild.MembersInternal ??= new ConcurrentDictionary<ulong, DiscordMember>();
guild.ScheduledEventsInternal ??= new ConcurrentDictionary<ulong, DiscordScheduledEvent>();
this.UpdateCachedGuild(eventGuild, rawMembers);
foreach (var xc in guild.ChannelsInternal.Values)
{
xc.GuildId = guild.Id;
xc.Initialize(this);
}
foreach (var xc in guild.ThreadsInternal.Values)
{
xc.GuildId = guild.Id;
xc.Discord = this;
}
foreach (var xe in guild.EmojisInternal.Values)
xe.Discord = this;
foreach (var xs in guild.StickersInternal.Values)
xs.Discord = this;
foreach (var xvs in guild.VoiceStatesInternal.Values)
xvs.Discord = this;
foreach (var xr in guild.RolesInternal.Values)
{
xr.Discord = this;
xr.GuildId = guild.Id;
}
foreach (var xsi in guild.StageInstancesInternal.Values)
{
xsi.Discord = this;
xsi.GuildId = guild.Id;
}
foreach (var xse in guild.ScheduledEventsInternal.Values)
{
xse.Discord = this;
xse.GuildId = guild.Id;
if (xse.Creator != null)
xse.Creator.Discord = this;
}
await this._guildUpdated.InvokeAsync(this, new GuildUpdateEventArgs(this.ServiceProvider) { GuildBefore = oldGuild, GuildAfter = guild }).ConfigureAwait(false);
}
/// <summary>
/// Handles the guild delete event.
/// </summary>
/// <param name="guild">The guild.</param>
internal async Task OnGuildDeleteEventAsync(DiscordGuild guild)
{
if (guild.IsUnavailable)
{
if (!this.GuildsInternal.TryGetValue(guild.Id, out var gld))
return;
gld.IsUnavailable = true;
await this._guildUnavailable.InvokeAsync(this, new GuildDeleteEventArgs(this.ServiceProvider) { Guild = guild, Unavailable = true }).ConfigureAwait(false);
}
else
{
if (!this.GuildsInternal.TryRemove(guild.Id, out var gld))
return;
await this._guildDeleted.InvokeAsync(this, new GuildDeleteEventArgs(this.ServiceProvider) { Guild = gld }).ConfigureAwait(false);
}
}
/// <summary>
/// Handles the guild audit log entry create event.
/// </summary>
/// <param name="guild">The guild where the audit log entry was created.</param>
/// <param name="auditLogCreateEntry">The auditlog event.</param>
internal async Task OnGuildAuditLogEntryCreateEventAsync(DiscordGuild guild, JObject auditLogCreateEntry)
{
this.Logger.LogDebug("New event: Audit log entry created");
this.Logger.LogDebug(auditLogCreateEntry.ToString(Newtonsoft.Json.Formatting.Indented));
var auditLogAction = DiscordJson.ToDiscordObject<AuditLogAction>(auditLogCreateEntry);
List<AuditLog> workaroundAuditLogEntryList = new()
{
new AuditLog()
{
Entries = new List<AuditLogAction>()
{
auditLogAction
}
}
};
var dataList = await guild.ProcessAuditLog(workaroundAuditLogEntryList);
var data = dataList.First();
await this._guildAuditLogEntryCreated.InvokeAsync(this, new(this.ServiceProvider) { Guild = guild, AuditLogEntry = data });
}
/// <summary>
/// Handles the guild sync event.
/// </summary>
/// <param name="guild">The guild.</param>
/// <param name="isLarge">Whether the guild is a large guild..</param>
/// <param name="rawMembers">The raw members.</param>
/// <param name="presences">The presences.</param>
internal async Task OnGuildSyncEventAsync(DiscordGuild guild, bool isLarge, JArray rawMembers, IEnumerable<DiscordPresence> presences)
{
presences = presences.Select(xp => { xp.Discord = this; xp.Activity = new DiscordActivity(xp.RawActivity); return xp; });
foreach (var xp in presences)
this.PresencesInternal[xp.InternalUser.Id] = xp;
guild.IsSynced = true;
guild.IsLarge = isLarge;
this.UpdateCachedGuild(guild, rawMembers);
await this._guildAvailable.InvokeAsync(this, new GuildCreateEventArgs(this.ServiceProvider) { Guild = guild }).ConfigureAwait(false);
}
/// <summary>
/// Handles the guild emojis update event.
/// </summary>
/// <param name="guild">The guild.</param>
/// <param name="newEmojis">The new emojis.</param>
internal async Task OnGuildEmojisUpdateEventAsync(DiscordGuild guild, IEnumerable<DiscordEmoji> newEmojis)
{
var oldEmojis = new ConcurrentDictionary<ulong, DiscordEmoji>(guild.EmojisInternal);
guild.EmojisInternal.Clear();
foreach (var emoji in newEmojis)
{
emoji.Discord = this;
guild.EmojisInternal[emoji.Id] = emoji;
}
var ea = new GuildEmojisUpdateEventArgs(this.ServiceProvider)
{
Guild = guild,
EmojisAfter = guild.Emojis,
EmojisBefore = new ReadOnlyConcurrentDictionary<ulong, DiscordEmoji>(oldEmojis)
};
await this._guildEmojisUpdated.InvokeAsync(this, ea).ConfigureAwait(false);
}
/// <summary>
/// Handles the stickers updated.
/// </summary>
/// <param name="newStickers">The new stickers.</param>
/// <param name="guildId">The guild id.</param>
internal async Task OnStickersUpdatedAsync(IEnumerable<DiscordSticker> newStickers, ulong guildId)
{
var guild = this.InternalGetCachedGuild(guildId);
var oldStickers = new ConcurrentDictionary<ulong, DiscordSticker>(guild.StickersInternal);
guild.StickersInternal.Clear();
foreach (var nst in newStickers)
{
if (nst.User is not null)
{
nst.User.Discord = this;
this.UserCache.AddOrUpdate(nst.User.Id, nst.User, (old, @new) => @new);
}
nst.Discord = this;
guild.StickersInternal[nst.Id] = nst;
}
var sea = new GuildStickersUpdateEventArgs(this.ServiceProvider)
{
Guild = guild,
StickersBefore = oldStickers,
StickersAfter = guild.Stickers
};
await this._guildStickersUpdated.InvokeAsync(this, sea).ConfigureAwait(false);
}
/// <summary>
/// Handles the created rule.
/// </summary>
/// <param name="newRule">The new added rule.</param>
internal async Task OnAutomodRuleCreated(AutomodRule newRule)
{
var sea = new AutomodRuleCreateEventArgs(this.ServiceProvider)
{
Rule = newRule
};
await this._automodRuleCreated.InvokeAsync(this, sea).ConfigureAwait(false);
}
/// <summary>
/// Handles the updated rule.
/// </summary>
/// <param name="updatedRule">The updated rule.</param>
internal async Task OnAutomodRuleUpdated(AutomodRule updatedRule)
{
var sea = new AutomodRuleUpdateEventArgs(this.ServiceProvider)
{
Rule = updatedRule
};
await this._automodRuleUpdated.InvokeAsync(this, sea).ConfigureAwait(false);
}
/// <summary>
/// Handles the deleted rule.
/// </summary>
/// <param name="deletedRule">The deleted rule.</param>
internal async Task OnAutomodRuleDeleted(AutomodRule deletedRule)
{
var sea = new AutomodRuleDeleteEventArgs(this.ServiceProvider)
{
Rule = deletedRule
};
await this._automodRuleDeleted.InvokeAsync(this, sea).ConfigureAwait(false);
}
/// <summary>
/// Handles the rule action execution.
/// </summary>
/// <param name="guild">The guild.</param>
/// <param name="rawPayload">The raw payload.</param>
internal async Task OnAutomodActionExecuted(DiscordGuild guild, JObject rawPayload)
{
var executedAction = rawPayload["action"].ToObject<AutomodAction>();
var ruleId = (ulong)rawPayload["rule_id"];
var triggerType = rawPayload["rule_trigger_type"].ToObject<AutomodTriggerType>();
var userId = (ulong)rawPayload["user_id"];
var channelId = rawPayload.ContainsKey("channel_id") ? (ulong?)rawPayload["channel_id"] : null;
var messageId = rawPayload.ContainsKey("message_id") ? (ulong?)rawPayload["message_id"] : null;
var alertMessageId = rawPayload.ContainsKey("alert_system_message_id") ? (ulong?)rawPayload["alert_system_message_id"] : null;
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
string? content = rawPayload.ContainsKey("content") ?(string?)rawPayload["content"] : null;
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
string? matchedKeyword = rawPayload.ContainsKey("matched_keyword") ? (string?)rawPayload["matched_keyword"] : null;
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
string? matchedContent = rawPayload.ContainsKey("matched_content") ? (string?)rawPayload["matched_content"] : null;
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
var ea = new AutomodActionExecutedEventArgs(this.ServiceProvider)
{
Guild = guild,
Action = executedAction,
RuleId = ruleId,
TriggerType = triggerType,
UserId = userId,
ChannelId = channelId,
MessageId = messageId,
AlertMessageId = alertMessageId,
MessageContent = content,
MatchedKeyword = matchedKeyword,
MatchedContent = matchedContent
};
await this._automodActionExecuted.InvokeAsync(this, ea).ConfigureAwait(false);
}
#endregion
#region Guild Ban
/// <summary>
/// Handles the guild ban add event.
/// </summary>
/// <param name="user">The transport user.</param>
/// <param name="guild">The guild.</param>
internal async Task OnGuildBanAddEventAsync(TransportUser user, DiscordGuild guild)
{
var usr = new DiscordUser(user) { Discord = this };
usr = this.UserCache.AddOrUpdate(user.Id, usr, (id, old) =>
{
old.Username = usr.Username;
old.Discriminator = usr.Discriminator;
old.AvatarHash = usr.AvatarHash;
return old;
});
if (!guild.Members.TryGetValue(user.Id, out var mbr))
mbr = new DiscordMember(usr) { Discord = this, GuildId = guild.Id };
var ea = new GuildBanAddEventArgs(this.ServiceProvider)
{
Guild = guild,
Member = mbr
};
await this._guildBanAdded.InvokeAsync(this, ea).ConfigureAwait(false);
}
/// <summary>
/// Handles the guild ban remove event.
/// </summary>
/// <param name="user">The transport user.</param>
/// <param name="guild">The guild.</param>
internal async Task OnGuildBanRemoveEventAsync(TransportUser user, DiscordGuild guild)
{
var usr = new DiscordUser(user) { Discord = this };
usr = this.UserCache.AddOrUpdate(user.Id, usr, (id, old) =>
{
old.Username = usr.Username;
old.Discriminator = usr.Discriminator;
old.AvatarHash = usr.AvatarHash;
return old;
});
if (!guild.Members.TryGetValue(user.Id, out var mbr))
mbr = new DiscordMember(usr) { Discord = this, GuildId = guild.Id };
var ea = new GuildBanRemoveEventArgs(this.ServiceProvider)
{
Guild = guild,
Member = mbr
};
await this._guildBanRemoved.InvokeAsync(this, ea).ConfigureAwait(false);
}
#endregion
#region Guild Scheduled Event
/// <summary>
/// Handles the scheduled event create event.
/// </summary>
/// <param name="scheduledEvent">The created event.</param>
/// <param name="guild">The guild.</param>
internal async Task OnGuildScheduledEventCreateEventAsync(DiscordScheduledEvent scheduledEvent, DiscordGuild guild)
{
scheduledEvent.Discord = this;
guild.ScheduledEventsInternal.AddOrUpdate(scheduledEvent.Id, scheduledEvent, (old, newScheduledEvent) => newScheduledEvent);
if (scheduledEvent.Creator != null)
{
scheduledEvent.Creator.Discord = this;
this.UserCache.AddOrUpdate(scheduledEvent.Creator.Id, scheduledEvent.Creator, (id, old) =>
{
old.Username = scheduledEvent.Creator.Username;
old.Discriminator = scheduledEvent.Creator.Discriminator;
old.AvatarHash = scheduledEvent.Creator.AvatarHash;
old.Flags = scheduledEvent.Creator.Flags;
return old;
});
}
await this._guildScheduledEventCreated.InvokeAsync(this, new GuildScheduledEventCreateEventArgs(this.ServiceProvider) { ScheduledEvent = scheduledEvent, Guild = scheduledEvent.Guild }).ConfigureAwait(false);
}
/// <summary>
/// Handles the scheduled event update event.
/// </summary>
/// <param name="scheduledEvent">The updated event.</param>
/// <param name="guild">The guild.</param>
internal async Task OnGuildScheduledEventUpdateEventAsync(DiscordScheduledEvent scheduledEvent, DiscordGuild guild)
{
if (guild == null)
return;
DiscordScheduledEvent oldEvent;
if (!guild.ScheduledEventsInternal.ContainsKey(scheduledEvent.Id))
{
oldEvent = null;
}
else
{
var ev = guild.ScheduledEventsInternal[scheduledEvent.Id];
oldEvent = new DiscordScheduledEvent
{
Id = ev.Id,
ChannelId = ev.ChannelId,
EntityId = ev.EntityId,
EntityMetadata = ev.EntityMetadata,
CreatorId = ev.CreatorId,
Creator = ev.Creator,
Discord = this,
Description = ev.Description,
EntityType = ev.EntityType,
ScheduledStartTimeRaw = ev.ScheduledStartTimeRaw,
ScheduledEndTimeRaw = ev.ScheduledEndTimeRaw,
GuildId = ev.GuildId,
Status = ev.Status,
Name = ev.Name,
UserCount = ev.UserCount,
CoverImageHash = ev.CoverImageHash
};
}
if (scheduledEvent.Creator != null)
{
scheduledEvent.Creator.Discord = this;
this.UserCache.AddOrUpdate(scheduledEvent.Creator.Id, scheduledEvent.Creator, (id, old) =>
{
old.Username = scheduledEvent.Creator.Username;
old.Discriminator = scheduledEvent.Creator.Discriminator;
old.AvatarHash = scheduledEvent.Creator.AvatarHash;
old.Flags = scheduledEvent.Creator.Flags;
return old;
});
}
if (scheduledEvent.Status == ScheduledEventStatus.Completed)
{
guild.ScheduledEventsInternal.TryRemove(scheduledEvent.Id, out var deletedEvent);
await this._guildScheduledEventDeleted.InvokeAsync(this, new GuildScheduledEventDeleteEventArgs(this.ServiceProvider) { ScheduledEvent = scheduledEvent, Guild = guild, Reason = ScheduledEventStatus.Completed }).ConfigureAwait(false);
}
else if (scheduledEvent.Status == ScheduledEventStatus.Canceled)
{
guild.ScheduledEventsInternal.TryRemove(scheduledEvent.Id, out var deletedEvent);
scheduledEvent.Status = ScheduledEventStatus.Canceled;
await this._guildScheduledEventDeleted.InvokeAsync(this, new GuildScheduledEventDeleteEventArgs(this.ServiceProvider) { ScheduledEvent = scheduledEvent, Guild = guild, Reason = ScheduledEventStatus.Canceled }).ConfigureAwait(false);
}
else
{
this.UpdateScheduledEvent(scheduledEvent, guild);
await this._guildScheduledEventUpdated.InvokeAsync(this, new GuildScheduledEventUpdateEventArgs(this.ServiceProvider) { ScheduledEventBefore = oldEvent, ScheduledEventAfter = scheduledEvent, Guild = guild }).ConfigureAwait(false);
}
}
/// <summary>
/// Handles the scheduled event delete event.
/// </summary>
/// <param name="scheduledEvent">The deleted event.</param>
/// <param name="guild">The guild.</param>
internal async Task OnGuildScheduledEventDeleteEventAsync(DiscordScheduledEvent scheduledEvent, DiscordGuild guild)
{
scheduledEvent.Discord = this;
if (scheduledEvent.Status == ScheduledEventStatus.Scheduled)
scheduledEvent.Status = ScheduledEventStatus.Canceled;
if (scheduledEvent.Creator != null)
{
scheduledEvent.Creator.Discord = this;
this.UserCache.AddOrUpdate(scheduledEvent.Creator.Id, scheduledEvent.Creator, (id, old) =>
{
old.Username = scheduledEvent.Creator.Username;
old.Discriminator = scheduledEvent.Creator.Discriminator;
old.AvatarHash = scheduledEvent.Creator.AvatarHash;
old.Flags = scheduledEvent.Creator.Flags;
return old;
});
}
await this._guildScheduledEventDeleted.InvokeAsync(this, new GuildScheduledEventDeleteEventArgs(this.ServiceProvider) { ScheduledEvent = scheduledEvent, Guild = scheduledEvent.Guild, Reason = scheduledEvent.Status }).ConfigureAwait(false);
guild.ScheduledEventsInternal.TryRemove(scheduledEvent.Id, out var deletedEvent);
}
/// <summary>
/// Handles the scheduled event user add event.
/// <param name="guildScheduledEventId">The event.</param>
/// <param name="userId">The added user id.</param>
/// <param name="guild">The guild.</param>
/// </summary>
internal async Task OnGuildScheduledEventUserAddedEventAsync(ulong guildScheduledEventId, ulong userId, DiscordGuild guild)
{
var scheduledEvent = this.InternalGetCachedScheduledEvent(guildScheduledEventId) ?? this.UpdateScheduledEvent(new DiscordScheduledEvent
{
Id = guildScheduledEventId,
GuildId = guild.Id,
Discord = this,
UserCount = 0
}, guild);
scheduledEvent.UserCount++;
scheduledEvent.Discord = this;
guild.Discord = this;
var user = this.GetUserAsync(userId, true).Result;
user.Discord = this;
var member = guild.Members.TryGetValue(userId, out var mem) ? mem : guild.GetMemberAsync(userId).Result;
member.Discord = this;
await this._guildScheduledEventUserAdded.InvokeAsync(this, new GuildScheduledEventUserAddEventArgs(this.ServiceProvider) { ScheduledEvent = scheduledEvent, Guild = guild, User = user, Member = member }).ConfigureAwait(false);
}
/// <summary>
/// Handles the scheduled event user remove event.
/// <param name="guildScheduledEventId">The event.</param>
/// <param name="userId">The removed user id.</param>
/// <param name="guild">The guild.</param>
/// </summary>
internal async Task OnGuildScheduledEventUserRemovedEventAsync(ulong guildScheduledEventId, ulong userId, DiscordGuild guild)
{
var scheduledEvent = this.InternalGetCachedScheduledEvent(guildScheduledEventId) ?? this.UpdateScheduledEvent(new DiscordScheduledEvent
{
Id = guildScheduledEventId,
GuildId = guild.Id,
Discord = this,
UserCount = 0
}, guild);
scheduledEvent.UserCount = scheduledEvent.UserCount == 0 ? 0 : scheduledEvent.UserCount - 1;
scheduledEvent.Discord = this;
guild.Discord = this;
var user = this.GetUserAsync(userId, true).Result;
user.Discord = this;
var member = guild.Members.TryGetValue(userId, out var mem) ? mem : guild.GetMemberAsync(userId).Result;
member.Discord = this;
await this._guildScheduledEventUserRemoved.InvokeAsync(this, new GuildScheduledEventUserRemoveEventArgs(this.ServiceProvider) { ScheduledEvent = scheduledEvent, Guild = guild, User = user, Member = member }).ConfigureAwait(false);
}
#endregion
#region Guild Integration
/// <summary>
/// Handles the guild integration create event.
/// </summary>
/// <param name="guild">The guild.</param>
/// <param name="integration">The integration.</param>
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);
}
/// <summary>
/// Handles the guild integration update event.
/// </summary>
/// <param name="guild">The guild.</param>
/// <param name="integration">The integration.</param>
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);
}
/// <summary>
/// Handles the guild integrations update event.
/// </summary>
/// <param name="guild">The guild.</param>
internal async Task OnGuildIntegrationsUpdateEventAsync(DiscordGuild guild)
{
var ea = new GuildIntegrationsUpdateEventArgs(this.ServiceProvider)
{
Guild = guild
};
await this._guildIntegrationsUpdated.InvokeAsync(this, ea).ConfigureAwait(false);
}
/// <summary>
/// Handles the guild integration delete event.
/// </summary>
/// <param name="guild">The guild.</param>
/// <param name="integrationId">The integration id.</param>
/// <param name="applicationId">The optional application id.</param>
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
/// <summary>
/// Handles the guild member add event.
/// </summary>
/// <param name="member">The transport member.</param>
/// <param name="guild">The guild.</param>
internal async Task OnGuildMemberAddEventAsync(TransportMember member, DiscordGuild guild)
{
var usr = new DiscordUser(member.User) { Discord = this };
usr = this.UserCache.AddOrUpdate(member.User.Id, usr, (id, old) =>
{
old.Username = usr.Username;
old.Discriminator = usr.Discriminator;
old.AvatarHash = usr.AvatarHash;
return old;
});
var mbr = new DiscordMember(member)
{
Discord = this,
GuildId = guild.Id
};
guild.MembersInternal[mbr.Id] = mbr;
guild.MemberCount++;
var ea = new GuildMemberAddEventArgs(this.ServiceProvider)
{
Guild = guild,
Member = mbr
};
await this._guildMemberAdded.InvokeAsync(this, ea).ConfigureAwait(false);
}
/// <summary>
/// Handles the guild member remove event.
/// </summary>
/// <param name="user">The transport user.</param>
/// <param name="guild">The guild.</param>
internal async Task OnGuildMemberRemoveEventAsync(TransportUser user, DiscordGuild guild)
{
var usr = new DiscordUser(user);
if (!guild.MembersInternal.TryRemove(user.Id, out var mbr))
mbr = new DiscordMember(usr) { Discord = this, GuildId = guild.Id };
guild.MemberCount--;
_ = this.UserCache.AddOrUpdate(user.Id, usr, (old, @new) => @new);
var ea = new GuildMemberRemoveEventArgs(this.ServiceProvider)
{
Guild = guild,
Member = mbr
};
await this._guildMemberRemoved.InvokeAsync(this, ea).ConfigureAwait(false);
}
/// <summary>
/// Handles the guild member update event.
/// </summary>
/// <param name="member">The transport member.</param>
/// <param name="guild">The guild.</param>
/// <param name="roles">The roles.</param>
/// <param name="nick">The nick.</param>
/// <param name="pending">Whether the member is pending.</param>
internal async Task OnGuildMemberUpdateEventAsync(TransportMember member, DiscordGuild guild, IEnumerable<ulong> roles, string nick, bool? pending)
{
var usr = new DiscordUser(member.User) { Discord = this };
usr = this.UserCache.AddOrUpdate(usr.Id, usr, (id, old) =>
{
old.Username = usr.Username;
old.Discriminator = usr.Discriminator;
old.AvatarHash = usr.AvatarHash;
return old;
});
if (!guild.Members.TryGetValue(member.User.Id, out var mbr))
mbr = new DiscordMember(usr) { Discord = this, GuildId = guild.Id };
var old = mbr;
var gAvOld = old.GuildAvatarHash;
var avOld = old.AvatarHash;
var nickOld = mbr.Nickname;
var pendingOld = mbr.IsPending;
var rolesOld = new ReadOnlyCollection<DiscordRole>(new List<DiscordRole>(mbr.Roles));
var cduOld = mbr.CommunicationDisabledUntil;
mbr.MemberFlags = member.MemberFlags;
mbr.AvatarHashInternal = member.AvatarHash;
mbr.GuildAvatarHash = member.GuildAvatarHash;
mbr.Nickname = nick;
mbr.GuildPronouns = member.GuildPronouns;
mbr.IsPending = pending;
mbr.CommunicationDisabledUntil = member.CommunicationDisabledUntil;
mbr.RoleIdsInternal.Clear();
mbr.RoleIdsInternal.AddRange(roles);
guild.MembersInternal.AddOrUpdate(member.User.Id, mbr, (id, oldMbr) => oldMbr);
var timeoutUntil = member.CommunicationDisabledUntil;
/*this.Logger.LogTrace($"Timeout:\nBefore - {cduOld}\nAfter - {timeoutUntil}");
if ((timeoutUntil.HasValue && cduOld.HasValue) || (timeoutUntil == null && cduOld.HasValue) || (timeoutUntil.HasValue && cduOld == null))
{
// We are going to add a scheduled timer to assure that we get a auditlog entry.
var id = $"tt-{mbr.Id}-{guild.Id}-{DateTime.Now.ToLongTimeString()}";
this._tempTimers.Add(
id,
new(
new TimeoutHandler(
mbr,
guild,
cduOld,
timeoutUntil
),
new Timer(
this.TimeoutTimer,
id,
2000,
Timeout.Infinite
)
)
);
this.Logger.LogTrace("Scheduling timeout event.");
return;
}*/
//this.Logger.LogTrace("No timeout detected. Continuing on normal operation.");
var eargs = new GuildMemberUpdateEventArgs(this.ServiceProvider)
{
Guild = guild,
Member = mbr,
NicknameAfter = mbr.Nickname,
RolesAfter = new ReadOnlyCollection<DiscordRole>(new List<DiscordRole>(mbr.Roles)),
PendingAfter = mbr.IsPending,
TimeoutAfter = mbr.CommunicationDisabledUntil,
AvatarHashAfter = mbr.AvatarHash,
GuildAvatarHashAfter = mbr.GuildAvatarHash,
NicknameBefore = nickOld,
RolesBefore = rolesOld,
PendingBefore = pendingOld,
TimeoutBefore = cduOld,
AvatarHashBefore = avOld,
GuildAvatarHashBefore = gAvOld
};
await this._guildMemberUpdated.InvokeAsync(this, eargs).ConfigureAwait(false);
}
/// <summary>
/// Handles timeout events.
/// </summary>
/// <param name="state">Internally used as uid for the timer data.</param>
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "<Pending>")]
private async void TimeoutTimer(object state)
{
var tid = (string)state;
var data = this._tempTimers.First(x=> x.Key == tid).Value.Key;
var timer = this._tempTimers.First(x=> x.Key == tid).Value.Value;
IReadOnlyList<DiscordAuditLogEntry> auditlog = null;
DiscordAuditLogMemberUpdateEntry filtered = null;
try
{
auditlog = await data.Guild.GetAuditLogsAsync(10, null, AuditLogActionType.MemberUpdate);
var preFiltered = auditlog.Select(x => x as DiscordAuditLogMemberUpdateEntry).Where(x => x.Target.Id == data.Member.Id);
filtered = preFiltered.First();
}
catch (UnauthorizedException) { }
catch (Exception)
{
this.Logger.LogTrace("Failing timeout event.");
await timer.DisposeAsync();
this._tempTimers.Remove(tid);
return;
}
var actor = filtered?.UserResponsible as DiscordMember;
this.Logger.LogTrace("Trying to execute timeout event.");
if (data.TimeoutUntilOld.HasValue && data.TimeoutUntilNew.HasValue)
{
// A timeout was updated.
if (filtered != null && auditlog == null)
{
this.Logger.LogTrace("Re-scheduling timeout event.");
timer.Change(2000, Timeout.Infinite);
return;
}
var ea = new GuildMemberTimeoutUpdateEventArgs(this.ServiceProvider)
{
Guild = data.Guild,
Target = data.Member,
TimeoutBefore = data.TimeoutUntilOld.Value,
TimeoutAfter = data.TimeoutUntilNew.Value,
Actor = actor,
AuditLogId = filtered?.Id,
AuditLogReason = filtered?.Reason
};
await this._guildMemberTimeoutChanged.InvokeAsync(this, ea).ConfigureAwait(false);
}
else if (!data.TimeoutUntilOld.HasValue && data.TimeoutUntilNew.HasValue)
{
// A timeout was added.
if (filtered != null && auditlog == null)
{
this.Logger.LogTrace("Re-scheduling timeout event.");
timer.Change(2000, Timeout.Infinite);
return;
}
var ea = new GuildMemberTimeoutAddEventArgs(this.ServiceProvider)
{
Guild = data.Guild,
Target = data.Member,
Timeout = data.TimeoutUntilNew.Value,
Actor = actor,
AuditLogId = filtered?.Id,
AuditLogReason = filtered?.Reason
};
await this._guildMemberTimeoutAdded.InvokeAsync(this, ea).ConfigureAwait(false);
}
else if (data.TimeoutUntilOld.HasValue && !data.TimeoutUntilNew.HasValue)
{
// A timeout was removed.
if (filtered != null && auditlog == null)
{
this.Logger.LogTrace("Re-scheduling timeout event.");
timer.Change(2000, Timeout.Infinite);
return;
}
var ea = new GuildMemberTimeoutRemoveEventArgs(this.ServiceProvider)
{
Guild = data.Guild,
Target = data.Member,
TimeoutBefore = data.TimeoutUntilOld.Value,
Actor = actor,
AuditLogId = filtered?.Id,
AuditLogReason = filtered?.Reason
};
await this._guildMemberTimeoutRemoved.InvokeAsync(this, ea).ConfigureAwait(false);
}
// Ending timer because it worked.
this.Logger.LogTrace("Removing timeout event.");
await timer.DisposeAsync();
this._tempTimers.Remove(tid);
}
/// <summary>
/// Handles the guild members chunk event.
/// </summary>
/// <param name="dat">The raw chunk data.</param>
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<DiscordMember>();
var pres = new HashSet<DiscordPresence>();
var members = dat["members"].ToObject<TransportMember[]>();
foreach (var member in members)
{
var mbr = new DiscordMember(member) { Discord = this, GuildId = guild.Id };
if (!this.UserCache.ContainsKey(mbr.Id))
this.UserCache[mbr.Id] = new DiscordUser(member.User) { Discord = this };
guild.MembersInternal[mbr.Id] = mbr;
mbrs.Add(mbr);
}
guild.MemberCount = guild.MembersInternal.Count;
var ea = new GuildMembersChunkEventArgs(this.ServiceProvider)
{
Guild = guild,
Members = new ReadOnlySet<DiscordMember>(mbrs),
ChunkIndex = chunkIndex,
ChunkCount = chunkCount,
Nonce = nonce,
};
if (dat["presences"] != null)
{
var presences = dat["presences"].ToObject<DiscordPresence[]>();
var presCount = presences.Length;
foreach (var presence in presences)
{
presence.Discord = this;
presence.Activity = new DiscordActivity(presence.RawActivity);
if (presence.RawActivities != null)
{
presence.InternalActivities = presence.RawActivities
.Select(x => new DiscordActivity(x)).ToArray();
}
pres.Add(presence);
}
ea.Presences = new ReadOnlySet<DiscordPresence>(pres);
}
if (dat["not_found"] != null)
{
var nf = dat["not_found"].ToObject<ISet<ulong>>();
ea.NotFound = new ReadOnlySet<ulong>(nf);
}
await this._guildMembersChunked.InvokeAsync(this, ea).ConfigureAwait(false);
}
#endregion
#region Guild Role
/// <summary>
/// Handles the guild role create event.
/// </summary>
/// <param name="role">The role.</param>
/// <param name="guild">The guild.</param>
internal async Task OnGuildRoleCreateEventAsync(DiscordRole role, DiscordGuild guild)
{
role.Discord = this;
role.GuildId = guild.Id;
guild.RolesInternal[role.Id] = role;
var ea = new GuildRoleCreateEventArgs(this.ServiceProvider)
{
Guild = guild,
Role = role
};
await this._guildRoleCreated.InvokeAsync(this, ea).ConfigureAwait(false);
}
/// <summary>
/// Handles the guild role update event.
/// </summary>
/// <param name="role">The role.</param>
/// <param name="guild">The guild.</param>
internal async Task OnGuildRoleUpdateEventAsync(DiscordRole role, DiscordGuild guild)
{
var newRole = guild.GetRole(role.Id);
var oldRole = new DiscordRole
{
GuildId = guild.Id,
ColorInternal = newRole.ColorInternal,
Discord = this,
IsHoisted = newRole.IsHoisted,
Id = newRole.Id,
IsManaged = newRole.IsManaged,
IsMentionable = newRole.IsMentionable,
Name = newRole.Name,
Permissions = newRole.Permissions,
Position = newRole.Position,
IconHash = newRole.IconHash,
Tags = newRole.Tags ?? null,
UnicodeEmojiString = newRole.UnicodeEmojiString
};
newRole.GuildId = guild.Id;
newRole.ColorInternal = role.ColorInternal;
newRole.IsHoisted = role.IsHoisted;
newRole.IsManaged = role.IsManaged;
newRole.IsMentionable = role.IsMentionable;
newRole.Name = role.Name;
newRole.Permissions = role.Permissions;
newRole.Position = role.Position;
newRole.IconHash = role.IconHash;
newRole.Tags = role.Tags ?? null;
newRole.UnicodeEmojiString = role.UnicodeEmojiString;
var ea = new GuildRoleUpdateEventArgs(this.ServiceProvider)
{
Guild = guild,
RoleAfter = newRole,
RoleBefore = oldRole
};
await this._guildRoleUpdated.InvokeAsync(this, ea).ConfigureAwait(false);
}
/// <summary>
/// Handles the guild role delete event.
/// </summary>
/// <param name="roleId">The role id.</param>
/// <param name="guild">The guild.</param>
internal async Task OnGuildRoleDeleteEventAsync(ulong roleId, DiscordGuild guild)
{
if (!guild.RolesInternal.TryRemove(roleId, out var role))
this.Logger.LogWarning($"Attempted to delete a nonexistent role ({roleId}) from guild ({guild}).");
var ea = new GuildRoleDeleteEventArgs(this.ServiceProvider)
{
Guild = guild,
Role = role
};
await this._guildRoleDeleted.InvokeAsync(this, ea).ConfigureAwait(false);
}
#endregion
#region Invite
/// <summary>
/// Handles the invite create event.
/// </summary>
/// <param name="channelId">The channel id.</param>
/// <param name="guildId">The guild id.</param>
/// <param name="invite">The invite.</param>
internal async Task OnInviteCreateEventAsync(ulong channelId, ulong guildId, DiscordInvite invite)
{
var guild = this.InternalGetCachedGuild(guildId);
var channel = this.InternalGetCachedChannel(channelId);
invite.Discord = this;
if (invite.Inviter is not null)
{
invite.Inviter.Discord = this;
this.UserCache.AddOrUpdate(invite.Inviter.Id, invite.Inviter, (old, @new) => @new);
}
guild.Invites[invite.Code] = invite;
var ea = new InviteCreateEventArgs(this.ServiceProvider)
{
Channel = channel,
Guild = guild,
Invite = invite
};
await this._inviteCreated.InvokeAsync(this, ea).ConfigureAwait(false);
}
/// <summary>
/// Handles the invite delete event.
/// </summary>
/// <param name="channelId">The channel id.</param>
/// <param name="guildId">The guild id.</param>
/// <param name="dat">The raw invite.</param>
internal async Task OnInviteDeleteEventAsync(ulong channelId, ulong guildId, JToken dat)
{
var guild = this.InternalGetCachedGuild(guildId);
var channel = this.InternalGetCachedChannel(channelId);
if (!guild.Invites.TryRemove(dat["code"].ToString(), out var invite))
{
invite = dat.ToObject<DiscordInvite>();
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
/// <summary>
/// Handles the message acknowledge event.
/// </summary>
/// <param name="chn">The channel.</param>
/// <param name="messageId">The message id.</param>
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);
}
/// <summary>
/// Handles the message create event.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="author">The transport user (author).</param>
/// <param name="member">The transport member.</param>
/// <param name="referenceAuthor">The reference transport user (author).</param>
/// <param name="referenceMember">The reference transport member.</param>
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<DiscordUser>(message.MentionedUsersInternal),
MentionedRoles = message.MentionedRolesInternal != null ? new ReadOnlyCollection<DiscordRole>(message.MentionedRolesInternal) : null,
MentionedChannels = message.MentionedChannelsInternal != null ? new ReadOnlyCollection<DiscordChannel>(message.MentionedChannelsInternal) : null
};
await this._messageCreated.InvokeAsync(this, ea).ConfigureAwait(false);
}
/// <summary>
/// Handles the message update event.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="author">The transport user (author).</param>
/// <param name="member">The transport member.</param>
/// <param name="referenceAuthor">The reference transport user (author).</param>
/// <param name="referenceMember">The reference transport member.</param>
internal async Task OnMessageUpdateEventAsync(DiscordMessage message, TransportUser author, TransportMember member, TransportUser referenceAuthor, TransportMember referenceMember)
{
DiscordGuild guild;
message.Discord = this;
var eventMessage = message;
DiscordMessage oldmsg = null;
if (this.Configuration.MessageCacheSize == 0
|| this.MessageCache == null
|| !this.MessageCache.TryGet(xm => xm.Id == eventMessage.Id && xm.ChannelId == eventMessage.ChannelId, out message))
{
message = eventMessage;
this.PopulateMessageReactionsAndCache(message, author, member);
guild = message.Channel?.Guild;
if (message.ReferencedMessage != null)
{
message.ReferencedMessage.Discord = this;
this.PopulateMessageReactionsAndCache(message.ReferencedMessage, referenceAuthor, referenceMember);
message.ReferencedMessage.PopulateMentions();
}
}
else
{
oldmsg = new DiscordMessage(message);
guild = message.Channel?.Guild;
message.EditedTimestampRaw = eventMessage.EditedTimestampRaw;
if (eventMessage.Content != null)
message.Content = eventMessage.Content;
message.EmbedsInternal.Clear();
message.EmbedsInternal.AddRange(eventMessage.EmbedsInternal);
message.Pinned = eventMessage.Pinned;
message.IsTts = eventMessage.IsTts;
}
message.PopulateMentions();
var ea = new MessageUpdateEventArgs(this.ServiceProvider)
{
Message = message,
MessageBefore = oldmsg,
MentionedUsers = new ReadOnlyCollection<DiscordUser>(message.MentionedUsersInternal),
MentionedRoles = message.MentionedRolesInternal != null ? new ReadOnlyCollection<DiscordRole>(message.MentionedRolesInternal) : null,
MentionedChannels = message.MentionedChannelsInternal != null ? new ReadOnlyCollection<DiscordChannel>(message.MentionedChannelsInternal) : null
};
await this._messageUpdated.InvokeAsync(this, ea).ConfigureAwait(false);
}
/// <summary>
/// Handles the message delete event.
/// </summary>
/// <param name="messageId">The message id.</param>
/// <param name="channelId">The channel id.</param>
/// <param name="guildId">The optional guild id.</param>
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);
}
/// <summary>
/// Handles the message bulk delete event.
/// </summary>
/// <param name="messageIds">The message ids.</param>
/// <param name="channelId">The channel id.</param>
/// <param name="guildId">The optional guild id.</param>
internal async Task OnMessageBulkDeleteEventAsync(ulong[] messageIds, ulong channelId, ulong? guildId)
{
var channel = this.InternalGetCachedChannel(channelId) ?? this.InternalGetCachedThread(channelId);
var msgs = new List<DiscordMessage>(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<DiscordMessage>(msgs),
Guild = guild
};
await this._messagesBulkDeleted.InvokeAsync(this, ea).ConfigureAwait(false);
}
#endregion
#region Message Reaction
/// <summary>
/// Handles the message reaction add event.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="messageId">The message id.</param>
/// <param name="channelId">The channel id.</param>
/// <param name="guildId">The optional guild id.</param>
/// <param name="mbr">The transport member.</param>
/// <param name="emoji">The emoji.</param>
/// <param name="isBurst">Whether a burst reaction was added.</param>
internal async Task OnMessageReactionAddAsync(ulong userId, ulong messageId, ulong channelId, ulong? guildId, TransportMember mbr, DiscordEmoji emoji, bool isBurst)
{
var channel = this.InternalGetCachedChannel(channelId) ?? this.InternalGetCachedThread(channelId);
var guild = this.InternalGetCachedGuild(guildId);
emoji.Discord = this;
var usr = this.UpdateUser(new DiscordUser { Id = userId, Discord = this }, guildId, guild, mbr);
if (channel == null
|| this.Configuration.MessageCacheSize == 0
|| this.MessageCache == null
|| !this.MessageCache.TryGet(xm => xm.Id == messageId && xm.ChannelId == channelId, out var msg))
{
msg = new DiscordMessage
{
Id = messageId,
ChannelId = channelId,
Discord = this,
ReactionsInternal = new List<DiscordReaction>()
};
}
var react = msg.ReactionsInternal.FirstOrDefault(xr => xr.Emoji == emoji);
if (react == null)
{
msg.ReactionsInternal.Add(react = new DiscordReaction
{
Count = 1,
Emoji = emoji,
IsMe = this.CurrentUser.Id == userId
});
}
else
{
react.Count++;
react.IsMe |= this.CurrentUser.Id == userId;
}
var ea = new MessageReactionAddEventArgs(this.ServiceProvider)
{
Message = msg,
User = usr,
Guild = guild,
Emoji = emoji,
IsBurst = isBurst,
Channel = channel,
ChannelId = channelId
};
await this._messageReactionAdded.InvokeAsync(this, ea).ConfigureAwait(false);
}
/// <summary>
/// Handles the message reaction remove event.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="messageId">The message id.</param>
/// <param name="channelId">The channel id.</param>
/// <param name="guildId">The guild id.</param>
/// <param name="emoji">The emoji.</param>
/// <param name="isBurst">Whether a burst reaction was added.</param>
internal async Task OnMessageReactionRemoveAsync(ulong userId, ulong messageId, ulong channelId, ulong? guildId, DiscordEmoji emoji, bool isBurst)
{
- var channel = this.InternalGetCachedChannel(channelId) ?? this.InternalGetCachedThread(channelId) ?? new DiscordChannel() {
- Type = ChannelType.Unknown, Id = channelId, GuildId = guildId, Discord = this
+ var channel = this.InternalGetCachedChannel(channelId) ?? this.InternalGetCachedThread(channelId) ?? new DiscordChannel()
+ {
+ Type = ChannelType.Unknown,
+ Id = channelId,
+ GuildId = guildId,
+ Discord = this
};
emoji.Discord = this;
if (!this.UserCache.TryGetValue(userId, out var usr))
usr = new DiscordUser { Id = userId, Discord = this };
if (channel?.Guild != null)
usr = channel.Guild.Members.TryGetValue(userId, out var member)
? member
: new DiscordMember(usr) { Discord = this, GuildId = channel.GuildId.Value };
if (channel == null
|| this.Configuration.MessageCacheSize == 0
|| this.MessageCache == null
|| !this.MessageCache.TryGet(xm => xm.Id == messageId && xm.ChannelId == channelId, out var msg))
{
msg = new DiscordMessage
{
Id = messageId,
ChannelId = channelId,
Discord = this
};
}
var react = msg.ReactionsInternal?.FirstOrDefault(xr => xr.Emoji == emoji);
if (react != null)
{
react.Count--;
react.IsMe &= this.CurrentUser.Id != userId;
if (msg.ReactionsInternal != null && react.Count <= 0) // shit happens
msg.ReactionsInternal.RemoveFirst(x => x.Emoji == emoji);
}
var guild = this.InternalGetCachedGuild(guildId);
var ea = new MessageReactionRemoveEventArgs(this.ServiceProvider)
{
Message = msg,
User = usr,
Guild = guild,
Emoji = emoji,
IsBurst = isBurst,
Channel = channel,
ChannelId = channelId
};
await this._messageReactionRemoved.InvokeAsync(this, ea).ConfigureAwait(false);
}
/// <summary>
/// Handles the message reaction remove event.
/// Fired when all message reactions were removed.
/// </summary>
/// <param name="messageId">The message id.</param>
/// <param name="channelId">The channel id.</param>
/// <param name="guildId">The optional guild id.</param>
internal async Task OnMessageReactionRemoveAllAsync(ulong messageId, ulong channelId, ulong? guildId)
{
var channel = this.InternalGetCachedChannel(channelId) ?? this.InternalGetCachedThread(channelId) ?? new DiscordChannel()
{
Type = ChannelType.Unknown,
Id = channelId,
GuildId = guildId,
Discord = this
};
if (channel == null
|| this.Configuration.MessageCacheSize == 0
|| this.MessageCache == null
|| !this.MessageCache.TryGet(xm => xm.Id == messageId && xm.ChannelId == channelId, out var msg))
{
msg = new DiscordMessage
{
Id = messageId,
ChannelId = channelId,
Discord = this
};
}
msg.ReactionsInternal?.Clear();
var guild = this.InternalGetCachedGuild(guildId);
var ea = new MessageReactionsClearEventArgs(this.ServiceProvider)
{
Message = msg
};
await this._messageReactionsCleared.InvokeAsync(this, ea).ConfigureAwait(false);
}
/// <summary>
/// Handles the message reaction remove event.
/// Fired when a emoji got removed.
/// </summary>
/// <param name="messageId">The message id.</param>
/// <param name="channelId">The channel id.</param>
/// <param name="guildId">The guild id.</param>
/// <param name="dat">The raw discord emoji.</param>
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<DiscordEmoji>();
if (!guild.EmojisInternal.TryGetValue(partialEmoji.Id, out var emoji))
{
emoji = partialEmoji;
emoji.Discord = this;
}
msg.ReactionsInternal?.RemoveAll(r => r.Emoji.Equals(emoji));
var ea = new MessageReactionRemoveEmojiEventArgs(this.ServiceProvider)
{
Channel = channel,
Guild = guild,
Message = msg,
Emoji = emoji
};
await this._messageReactionRemovedEmoji.InvokeAsync(this, ea).ConfigureAwait(false);
}
#endregion
#region Stage Instance
/// <summary>
/// Handles the stage instance create event.
/// </summary>
/// <param name="stage">The created stage instance.</param>
internal async Task OnStageInstanceCreateEventAsync(DiscordStageInstance stage)
{
stage.Discord = this;
var guild = this.InternalGetCachedGuild(stage.GuildId);
guild.StageInstancesInternal[stage.Id] = stage;
await this._stageInstanceCreated.InvokeAsync(this, new StageInstanceCreateEventArgs(this.ServiceProvider) { StageInstance = stage, Guild = stage.Guild }).ConfigureAwait(false);
}
/// <summary>
/// Handles the stage instance update event.
/// </summary>
/// <param name="stage">The updated stage instance.</param>
internal async Task OnStageInstanceUpdateEventAsync(DiscordStageInstance stage)
{
stage.Discord = this;
var guild = this.InternalGetCachedGuild(stage.GuildId);
guild.StageInstancesInternal[stage.Id] = stage;
await this._stageInstanceUpdated.InvokeAsync(this, new StageInstanceUpdateEventArgs(this.ServiceProvider) { StageInstance = stage, Guild = stage.Guild }).ConfigureAwait(false);
}
/// <summary>
/// Handles the stage instance delete event.
/// </summary>
/// <param name="stage">The deleted stage instance.</param>
internal async Task OnStageInstanceDeleteEventAsync(DiscordStageInstance stage)
{
stage.Discord = this;
var guild = this.InternalGetCachedGuild(stage.GuildId);
guild.StageInstancesInternal[stage.Id] = stage;
await this._stageInstanceDeleted.InvokeAsync(this, new StageInstanceDeleteEventArgs(this.ServiceProvider) { StageInstance = stage, Guild = stage.Guild }).ConfigureAwait(false);
}
#endregion
#region Thread
/// <summary>
/// Handles the thread create event.
/// </summary>
/// <param name="thread">The created thread.</param>
internal async Task OnThreadCreateEventAsync(DiscordThreadChannel thread)
{
thread.Discord = this;
this.InternalGetCachedGuild(thread.GuildId).ThreadsInternal.AddOrUpdate(thread.Id, thread, (oldThread, newThread) => newThread);
await this._threadCreated.InvokeAsync(this, new ThreadCreateEventArgs(this.ServiceProvider) { Thread = thread, Guild = thread.Guild, Parent = thread.Parent }).ConfigureAwait(false);
}
/// <summary>
/// Handles the thread update event.
/// </summary>
/// <param name="thread">The updated thread.</param>
internal async Task OnThreadUpdateEventAsync(DiscordThreadChannel thread)
{
if (thread == null)
return;
thread.Discord = this;
var guild = thread.Guild;
var threadNew = this.InternalGetCachedThread(thread.Id);
DiscordThreadChannel threadOld = null;
ThreadUpdateEventArgs updateEvent;
if (threadNew != null)
{
threadOld = new DiscordThreadChannel
{
Discord = this,
Type = threadNew.Type,
ThreadMetadata = thread.ThreadMetadata,
ThreadMembersInternal = threadNew.ThreadMembersInternal,
ParentId = thread.ParentId,
OwnerId = thread.OwnerId,
Name = thread.Name,
LastMessageId = threadNew.LastMessageId,
MessageCount = thread.MessageCount,
MemberCount = thread.MemberCount,
GuildId = thread.GuildId,
LastPinTimestampRaw = threadNew.LastPinTimestampRaw,
PerUserRateLimit = threadNew.PerUserRateLimit,
CurrentMember = threadNew.CurrentMember,
TotalMessagesSent = threadNew.TotalMessagesSent
};
if (this.Guilds != null)
{
if (thread.ParentId.HasValue && this.InternalGetCachedChannel(thread.ParentId.Value).Type == ChannelType.Forum)
{
threadOld.AppliedTagIdsInternal = threadNew.AppliedTagIdsInternal;
threadNew.AppliedTagIdsInternal = thread.AppliedTagIdsInternal;
}
else
{
threadOld.AppliedTagIdsInternal = null;
threadNew.AppliedTagIdsInternal = null;
}
}
else
{
threadOld.AppliedTagIdsInternal = threadNew.AppliedTagIdsInternal;
threadNew.AppliedTagIdsInternal = thread.AppliedTagIdsInternal;
}
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;
threadNew.Discord = this;
threadNew.TotalMessagesSent = thread.TotalMessagesSent;
updateEvent = new ThreadUpdateEventArgs(this.ServiceProvider)
{
ThreadAfter = thread,
ThreadBefore = threadOld,
Guild = thread.Guild,
Parent = thread.Parent
};
}
else
{
updateEvent = new ThreadUpdateEventArgs(this.ServiceProvider)
{
ThreadAfter = thread,
Guild = thread.Guild,
Parent = thread.Parent
};
guild.ThreadsInternal[thread.Id] = thread;
}
await this._threadUpdated.InvokeAsync(this, updateEvent).ConfigureAwait(false);
}
/// <summary>
/// Handles the thread delete event.
/// </summary>
/// <param name="thread">The deleted thread.</param>
internal async Task OnThreadDeleteEventAsync(DiscordThreadChannel thread)
{
if (thread == null)
return;
thread.Discord = this;
var gld = thread.Guild;
if (gld.ThreadsInternal.TryRemove(thread.Id, out var cachedThread))
thread = cachedThread;
await this._threadDeleted.InvokeAsync(this, new ThreadDeleteEventArgs(this.ServiceProvider) { Thread = thread, Guild = thread.Guild, Parent = thread.Parent, Type = thread.Type }).ConfigureAwait(false);
}
/// <summary>
/// Handles the thread list sync event.
/// </summary>
/// <param name="guild">The synced guild.</param>
/// <param name="channelIds">The synced channel ids.</param>
/// <param name="threads">The synced threads.</param>
/// <param name="members">The synced thread members.</param>
internal async Task OnThreadListSyncEventAsync(DiscordGuild guild, IReadOnlyList<ulong?> channelIds, IReadOnlyList<DiscordThreadChannel> threads, IReadOnlyList<DiscordThreadChannelMember> members)
{
guild.Discord = this;
var channels = channelIds.Select(x => guild.GetChannel(x.Value)); //getting channel objects
foreach (var chan in channels)
{
chan.Discord = this;
}
_ = threads.Select(x => x.Discord = this);
await this._threadListSynced.InvokeAsync(this, new ThreadListSyncEventArgs(this.ServiceProvider) { Guild = guild, Channels = channels.ToList().AsReadOnly(), Threads = threads, Members = members.ToList().AsReadOnly() }).ConfigureAwait(false);
}
/// <summary>
/// Handles the thread member update event.
/// </summary>
/// <param name="member">The updated member.</param>
internal async Task OnThreadMemberUpdateEventAsync(DiscordThreadChannelMember member)
{
member.Discord = this;
var thread = this.InternalGetCachedThread(member.Id);
if (thread == null)
{
var tempThread = await this.ApiClient.GetThreadAsync(member.Id);
thread = this.GuildsInternal[member.GuildId].ThreadsInternal.AddOrUpdate(member.Id, tempThread, (old, newThread) => newThread);
}
thread.CurrentMember = member;
thread.Guild.ThreadsInternal.AddOrUpdate(member.Id, thread, (oldThread, newThread) => newThread);
await this._threadMemberUpdated.InvokeAsync(this, new ThreadMemberUpdateEventArgs(this.ServiceProvider) { ThreadMember = member, Thread = thread }).ConfigureAwait(false);
}
/// <summary>
/// Handles the thread members update event.
/// </summary>
/// <param name="guild">The target guild.</param>
/// <param name="threadId">The thread id of the target thread this update belongs to.</param>
/// <param name="membersAdded">The added members.</param>
/// <param name="membersRemoved">The ids of the removed members.</param>
/// <param name="memberCount">The new member count.</param>
internal async Task OnThreadMembersUpdateEventAsync(DiscordGuild guild, ulong threadId, JArray membersAdded, JArray membersRemoved, int memberCount)
{
var thread = this.InternalGetCachedThread(threadId);
if (thread == null)
{
var tempThread = await this.ApiClient.GetThreadAsync(threadId);
thread = guild.ThreadsInternal.AddOrUpdate(threadId, tempThread, (old, newThread) => newThread);
}
thread.Discord = this;
guild.Discord = this;
List<DiscordThreadChannelMember> addedMembers = new();
List<ulong> removedMemberIds = new();
if (membersAdded != null)
{
foreach (var xj in membersAdded)
{
var xtm = xj.ToDiscordObject<DiscordThreadChannelMember>();
xtm.Discord = this;
xtm.GuildId = guild.Id;
if (xtm != null)
addedMembers.Add(xtm);
if (xtm.Id == this.CurrentUser.Id)
thread.CurrentMember = xtm;
}
}
var removedMembers = new List<DiscordMember>();
if (membersRemoved != null)
{
foreach (var removedId in membersRemoved)
{
removedMembers.Add(guild.MembersInternal.TryGetValue((ulong)removedId, out var member) ? member : new DiscordMember { Id = (ulong)removedId, GuildId = guild.Id, Discord = this });
}
}
if (removedMemberIds.Contains(this.CurrentUser.Id)) //indicates the bot was removed from the thread
thread.CurrentMember = null;
thread.MemberCount = memberCount;
var threadMembersUpdateArg = new ThreadMembersUpdateEventArgs(this.ServiceProvider)
{
Guild = guild,
Thread = thread,
AddedMembers = addedMembers,
RemovedMembers = removedMembers,
MemberCount = memberCount
};
await this._threadMembersUpdated.InvokeAsync(this, threadMembersUpdateArg).ConfigureAwait(false);
}
#endregion
#region Activities
/// <summary>
/// Dispatches the <see cref="EmbeddedActivityUpdated"/> event.
/// </summary>
/// <param name="trActivity">The transport activity.</param>
/// <param name="guild">The guild.</param>
/// <param name="channelId">The channel id.</param>
/// <param name="jUsers">The users in the activity.</param>
/// <param name="appId">The application id.</param>
internal async Task OnEmbeddedActivityUpdateAsync(JObject trActivity, DiscordGuild guild, ulong channelId, JArray jUsers, ulong appId)
=> await Task.Delay(20);
/*{
try
{
var users = j_users?.ToObject<List<ulong>>();
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<DiscordActivity>();
this._embeddedActivities[uid] = activity;
}
var activity_users = new List<DiscordMember>();
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
/// <summary>
/// Handles the presence update event.
/// </summary>
/// <param name="rawPresence">The raw presence.</param>
/// <param name="rawUser">The raw user.</param>
internal async Task OnPresenceUpdateEventAsync(JObject rawPresence, JObject rawUser)
{
var uid = (ulong)rawUser["id"];
DiscordPresence old = null;
if (this.PresencesInternal.TryGetValue(uid, out var presence))
{
old = new DiscordPresence(presence);
DiscordJson.PopulateObject(rawPresence, presence);
}
else
{
presence = rawPresence.ToObject<DiscordPresence>();
presence.Discord = this;
presence.Activity = new DiscordActivity(presence.RawActivity);
this.PresencesInternal[presence.InternalUser.Id] = presence;
}
// reuse arrays / avoid linq (this is a hot zone)
if (presence.Activities == null || rawPresence["activities"] == null)
{
presence.InternalActivities = Array.Empty<DiscordActivity>();
}
else
{
if (presence.InternalActivities.Length != presence.RawActivities.Length)
presence.InternalActivities = new DiscordActivity[presence.RawActivities.Length];
for (var i = 0; i < presence.InternalActivities.Length; i++)
presence.InternalActivities[i] = new DiscordActivity(presence.RawActivities[i]);
if (presence.InternalActivities.Length > 0)
{
presence.RawActivity = presence.RawActivities[0];
if (presence.Activity != null)
presence.Activity.UpdateWith(presence.RawActivity);
else
presence.Activity = new DiscordActivity(presence.RawActivity);
}
}
var ea = new PresenceUpdateEventArgs(this.ServiceProvider)
{
Status = presence.Status,
Activity = presence.Activity,
User = presence.User,
PresenceBefore = old,
PresenceAfter = presence
};
await this._presenceUpdated.InvokeAsync(this, ea).ConfigureAwait(false);
}
/// <summary>
/// Handles the user settings update event.
/// </summary>
/// <param name="user">The transport user.</param>
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);
}
/// <summary>
/// Handles the user update event.
/// </summary>
/// <param name="user">The transport user.</param>
internal async Task OnUserUpdateEventAsync(TransportUser user)
{
var usrOld = new DiscordUser
{
AvatarHash = this.CurrentUser.AvatarHash,
Discord = this,
Discriminator = this.CurrentUser.Discriminator,
Email = this.CurrentUser.Email,
Id = this.CurrentUser.Id,
IsBot = this.CurrentUser.IsBot,
MfaEnabled = this.CurrentUser.MfaEnabled,
Username = this.CurrentUser.Username,
Verified = this.CurrentUser.Verified
};
this.CurrentUser.AvatarHash = user.AvatarHash;
this.CurrentUser.Discriminator = user.Discriminator;
this.CurrentUser.Email = user.Email;
this.CurrentUser.Id = user.Id;
this.CurrentUser.IsBot = user.IsBot;
this.CurrentUser.MfaEnabled = user.MfaEnabled;
this.CurrentUser.Username = user.Username;
this.CurrentUser.Verified = user.Verified;
var ea = new UserUpdateEventArgs(this.ServiceProvider)
{
UserAfter = this.CurrentUser,
UserBefore = usrOld
};
await this._userUpdated.InvokeAsync(this, ea).ConfigureAwait(false);
}
#endregion
#region Voice
/// <summary>
/// Handles the voice state update event.
/// </summary>
/// <param name="raw">The raw voice state update object.</param>
internal async Task OnVoiceStateUpdateEventAsync(JObject raw)
{
var gid = (ulong)raw["guild_id"];
var uid = (ulong)raw["user_id"];
var gld = this.GuildsInternal[gid];
var vstateNew = raw.ToObject<DiscordVoiceState>();
vstateNew.Discord = this;
gld.VoiceStatesInternal.TryRemove(uid, out var vstateOld);
if (vstateNew.Channel != null)
{
gld.VoiceStatesInternal[vstateNew.UserId] = vstateNew;
}
if (gld.MembersInternal.TryGetValue(uid, out var mbr))
{
mbr.IsMuted = vstateNew.IsServerMuted;
mbr.IsDeafened = vstateNew.IsServerDeafened;
}
else
{
var transportMbr = vstateNew.TransportMember;
this.UpdateUser(new DiscordUser(transportMbr.User) { Discord = this }, gid, gld, transportMbr);
}
var ea = new VoiceStateUpdateEventArgs(this.ServiceProvider)
{
Guild = vstateNew.Guild,
Channel = vstateNew.Channel,
User = vstateNew.User,
SessionId = vstateNew.SessionId,
Before = vstateOld,
After = vstateNew
};
await this._voiceStateUpdated.InvokeAsync(this, ea).ConfigureAwait(false);
}
/// <summary>
/// Handles the voice server update event.
/// </summary>
/// <param name="endpoint">The new endpoint.</param>
/// <param name="token">The new token.</param>
/// <param name="guild">The guild.</param>
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
/// <summary>
/// Handles the application command create event.
/// </summary>
/// <param name="cmd">The application command.</param>
/// <param name="guildId">The optional guild id.</param>
internal async Task OnApplicationCommandCreateAsync(DiscordApplicationCommand cmd, ulong? guildId)
{
cmd.Discord = this;
var guild = this.InternalGetCachedGuild(guildId);
if (guild == null && guildId.HasValue)
{
guild = new DiscordGuild
{
Id = guildId.Value,
Discord = this
};
}
var ea = new ApplicationCommandEventArgs(this.ServiceProvider)
{
Guild = guild,
Command = cmd
};
await this._applicationCommandCreated.InvokeAsync(this, ea).ConfigureAwait(false);
}
/// <summary>
/// Handles the application command update event.
/// </summary>
/// <param name="cmd">The application command.</param>
/// <param name="guildId">The optional guild id.</param>
internal async Task OnApplicationCommandUpdateAsync(DiscordApplicationCommand cmd, ulong? guildId)
{
cmd.Discord = this;
var guild = this.InternalGetCachedGuild(guildId);
if (guild == null && guildId.HasValue)
{
guild = new DiscordGuild
{
Id = guildId.Value,
Discord = this
};
}
var ea = new ApplicationCommandEventArgs(this.ServiceProvider)
{
Guild = guild,
Command = cmd
};
await this._applicationCommandUpdated.InvokeAsync(this, ea).ConfigureAwait(false);
}
/// <summary>
/// Handles the application command delete event.
/// </summary>
/// <param name="cmd">The application command.</param>
/// <param name="guildId">The optional guild id.</param>
internal async Task OnApplicationCommandDeleteAsync(DiscordApplicationCommand cmd, ulong? guildId)
{
cmd.Discord = this;
var guild = this.InternalGetCachedGuild(guildId);
if (guild == null && guildId.HasValue)
{
guild = new DiscordGuild
{
Id = guildId.Value,
Discord = this
};
}
var ea = new ApplicationCommandEventArgs(this.ServiceProvider)
{
Guild = guild,
Command = cmd
};
await this._applicationCommandDeleted.InvokeAsync(this, ea).ConfigureAwait(false);
}
/// <summary>
/// Handles the guild application command counts update event.
/// </summary>
/// <param name="chatInputCommandCount">The <see cref="ApplicationCommandType.ChatInput"/> count.</param>
/// <param name="userContextMenuCommandCount">The <see cref="ApplicationCommandType.User"/> count.</param>
/// <param name="messageContextMenuCount">The <see cref="ApplicationCommandType.Message"/> count.</param>
/// <param name="guildId">The guild id.</param>
/// <returns>Count of application commands.</returns>
internal async Task OnGuildApplicationCommandCountsUpdateAsync(int chatInputCommandCount, int userContextMenuCommandCount, int messageContextMenuCount, ulong guildId)
{
var guild = this.InternalGetCachedGuild(guildId);
if (guild == null)
{
guild = new DiscordGuild
{
Id = guildId,
Discord = this
};
}
var ea = new GuildApplicationCommandCountEventArgs(this.ServiceProvider)
{
SlashCommands = chatInputCommandCount,
UserContextMenuCommands = userContextMenuCommandCount,
MessageContextMenuCommands = messageContextMenuCount,
Guild = guild
};
await this._guildApplicationCommandCountUpdated.InvokeAsync(this, ea).ConfigureAwait(false);
}
/// <summary>
/// Handles the application command permissions update event.
/// </summary>
/// <param name="perms">The new permissions.</param>
/// <param name="channelId">The command id.</param>
/// <param name="guildId">The guild id.</param>
/// <param name="applicationId">The application id.</param>
internal async Task OnApplicationCommandPermissionsUpdateAsync(IEnumerable<DiscordApplicationCommandPermission> perms, ulong channelId, ulong guildId, ulong applicationId)
{
if (applicationId != this.CurrentApplication.Id)
return;
var guild = this.InternalGetCachedGuild(guildId);
DiscordApplicationCommand cmd;
try
{
cmd = await this.GetGuildApplicationCommandAsync(guildId, channelId);
}
catch (NotFoundException)
{
cmd = await this.GetGlobalApplicationCommandAsync(channelId);
}
if (guild == null)
{
guild = new DiscordGuild
{
Id = guildId,
Discord = this
};
}
var ea = new ApplicationCommandPermissionsUpdateEventArgs(this.ServiceProvider)
{
Permissions = perms.ToList(),
Command = cmd,
ApplicationId = applicationId,
Guild = guild
};
await this._applicationCommandPermissionsUpdated.InvokeAsync(this, ea).ConfigureAwait(false);
}
#endregion
#region Interaction
/// <summary>
/// Handles the interaction create event.
/// </summary>
/// <param name="guildId">The guild id.</param>
/// <param name="channelId">The channel id.</param>
/// <param name="user">The transport user.</param>
/// <param name="member">The transport member.</param>
/// <param name="interaction">The interaction.</param>
/// <param name="rawInteraction">Debug.</param>
internal async Task OnInteractionCreateAsync(ulong? guildId, ulong channelId, TransportUser user, TransportMember member, DiscordInteraction interaction, string rawInteraction)
{
this.Logger.LogTrace("Interaction from {guild} on shard {shard}", guildId.HasValue ? guildId.Value : "dm", this.ShardId);
this.Logger.LogTrace("Interaction: {interaction}", rawInteraction);
var usr = new DiscordUser(user) { Discord = this };
interaction.ChannelId = channelId;
interaction.GuildId = guildId;
interaction.Discord = this;
interaction.Data.Discord = this;
if (member != null)
{
usr = new DiscordMember(member) { GuildId = guildId.Value, Discord = this };
this.UpdateUser(usr, guildId, interaction.Guild, member);
}
else
{
this.UserCache.AddOrUpdate(usr.Id, usr, (old, @new) => @new);
}
usr.Locale = interaction.Locale;
interaction.User = usr;
var resolved = interaction.Data.Resolved;
if (resolved != null)
{
if (resolved.Users != null)
{
foreach (var c in resolved.Users)
{
c.Value.Discord = this;
this.UserCache.AddOrUpdate(c.Value.Id, c.Value, (old, @new) => @new);
}
}
if (resolved.Members != null)
{
foreach (var c in resolved.Members)
{
c.Value.Discord = this;
c.Value.Id = c.Key;
c.Value.GuildId = guildId.Value;
c.Value.User.Discord = this;
this.UserCache.AddOrUpdate(c.Value.User.Id, c.Value.User, (old, @new) => @new);
}
}
if (resolved.Channels != null)
{
foreach (var c in resolved.Channels)
{
c.Value.Discord = this;
if (guildId.HasValue)
{
c.Value.GuildId = guildId.Value;
try
{
if (this.Guilds.TryGetValue(guildId.Value, out var guild))
if (guild.ChannelsInternal.TryGetValue(c.Key, out var channel) && channel.PermissionOverwritesInternal != null && channel.PermissionOverwritesInternal.Any())
c.Value.PermissionOverwritesInternal = channel.PermissionOverwritesInternal;
}
catch (Exception) { }
}
}
}
if (resolved.Roles != null)
{
foreach (var c in resolved.Roles)
{
c.Value.Discord = this;
if (guildId.HasValue)
c.Value.GuildId = guildId.Value;
}
}
if (resolved.Messages != null)
{
foreach (var m in resolved.Messages)
{
m.Value.Discord = this;
if (guildId.HasValue)
m.Value.GuildId = guildId.Value;
}
}
if (resolved.Attachments != null)
foreach (var a in resolved.Attachments)
a.Value.Discord = this;
}
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 ea = new ContextMenuInteractionCreateEventArgs(this.ServiceProvider)
{
Interaction = interaction,
TargetUser = targetMember ?? targetUser,
TargetMessage = targetMessage,
Type = interaction.Data.Type
};
await this._contextMenuInteractionCreated.InvokeAsync(this, ea);
}
else
{
var ea = new InteractionCreateEventArgs(this.ServiceProvider)
{
Interaction = interaction
};
await this._interactionCreated.InvokeAsync(this, ea);
}
}
}
#endregion
#region Misc
/// <summary>
/// Handles the typing start event.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="channelId">The channel id.</param>
/// <param name="channel">The channel.</param>
/// <param name="guildId">The optional guild id.</param>
/// <param name="started">The time when the user started typing.</param>
/// <param name="mbr">The transport member.</param>
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);
}
/// <summary>
/// Handles the webhooks update.
/// </summary>
/// <param name="channel">The channel.</param>
/// <param name="guild">The guild.</param>
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);
}
/// <summary>
/// Handles all unknown events.
/// </summary>
/// <param name="payload">The payload.</param>
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.EventHandlers.cs b/DisCatSharp/Clients/DiscordClient.EventHandlers.cs
index fbe2ee15b..3eaabddeb 100644
--- a/DisCatSharp/Clients/DiscordClient.EventHandlers.cs
+++ b/DisCatSharp/Clients/DiscordClient.EventHandlers.cs
@@ -1,201 +1,201 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
namespace DisCatSharp;
/// <summary>
/// A Discord API wrapper.
/// </summary>
public sealed partial class DiscordClient
{
private readonly Dictionary<(object?, Type, bool), List<(EventInfo, Delegate)[]>> _registrationToDelegate = new();
private readonly Dictionary<Type, List<object>> _typeToAnonymousHandlers = new();
/// <summary>
/// Registers all methods annotated with <see cref="EventAttribute"/> from the given object.
/// </summary>
/// <param name="handler">The event handler object.</param>
/// <param name="registerStatic">Whether to consider static methods.</param>
public void RegisterEventHandler(object handler, bool registerStatic = false)
=> this.RegisterEventHandlerImpl(handler, handler.GetType(), registerStatic);
/// <summary>
/// Registers all static methods annotated with <see cref="EventAttribute"/> from the given type.
/// </summary>
/// <param name="t">The static event handler type.</param>
public void RegisterStaticEventHandler(Type t)
=> this.RegisterEventHandlerImpl(null, t);
/// <see cref="RegisterStaticEventHandler(Type)"/>
public void RegisterStaticEventHandler<T>() => this.RegisterStaticEventHandler(typeof(T));
/// <summary>
/// <para>If abstract, registers all static methods of the type.</para>
/// <para>If non-abstract, tries to instantiate it, optionally using the provided <see cref="DiscordConfiguration.ServiceProvider"/>
/// and registers all instance and static methods.</para>
/// </summary>
/// <param name="type"></param>
public void RegisterEventHandler(Type type)
{
if (type.IsAbstract)
this.RegisterStaticEventHandler(type);
else
{
var anon = ActivatorUtilities.CreateInstance(this.Configuration.ServiceProvider, type);
this._typeToAnonymousHandlers[type]
= this._typeToAnonymousHandlers.TryGetValue(type, out var anonObjs) ? anonObjs : (anonObjs = new());
anonObjs.Add(anon);
this.RegisterEventHandlerImpl(anon, type);
}
}
/// <see cref="RegisterEventHandler(Type)"/>
public void RegisterEventHandler<T>() => this.RegisterEventHandler(typeof(T));
/// <summary>
/// Registers all types associated with the provided assembly that have the <see cref="EventHandler"/> attribute.
/// </summary>
/// <param name="assembly">The assembly from which to get the types.</param>
public void RegisterEventHandlers(Assembly assembly)
{
foreach (var t in GetEventHandlersFromAssembly(assembly))
this.RegisterEventHandler(t);
}
/// <summary>
/// Perfectly mirrors <see cref="RegisterEventHandler(object, bool)"/>.
/// </summary>
/// <param name="handler"></param>
/// <param name="wasRegisteredWithStatic"></param>
public void UnregisterEventHandler(object handler, bool wasRegisteredWithStatic = false)
=> this.UnregisterEventHandlerImpl(handler, handler.GetType(), wasRegisteredWithStatic);
/// <summary>
/// Perfectly mirrors <see cref="RegisterStaticEventHandler(Type)"/>.
/// </summary>
/// <param name="t"></param>
public void UnregisterStaticEventHandler(Type t) => this.UnregisterEventHandlerImpl(null, t);
/// <summary>
/// Perfectly mirrors <see cref="RegisterStaticEventHandler{T}()"/>.
/// </summary>
/// <typeparam name="T"></typeparam>
public void UnregisterStaticEventHandler<T>() => this.UnregisterEventHandler(typeof(T));
/// <summary>
/// Perfectly mirrors <see cref="RegisterEventHandler(Type)"/>.
/// </summary>
/// <param name="t"></param>
public void UnregisterEventHandler(Type t)
{
if (t.IsAbstract)
this.UnregisterStaticEventHandler(t);
else
{
if (!this._typeToAnonymousHandlers.TryGetValue(t, out var anonObjs)
|| anonObjs.Count == 0)
return; // Wasn't registered
var anon = anonObjs[0];
anonObjs.RemoveAt(0);
if (anonObjs.Count == 0)
this._typeToAnonymousHandlers.Remove(t);
this.UnregisterEventHandlerImpl(anon, t);
}
}
/// <summary>
/// Perfectly mirrors <see cref="RegisterEventHandler{T}()"/>.
/// </summary>
/// <typeparam name="T"></typeparam>
public void UnregisterEventHandler<T>() => this.UnregisterEventHandler(typeof(T));
/// <summary>
/// Perfectly mirrors <see cref="RegisterEventHandlers(Assembly)"/>.
/// </summary>
/// <param name="assembly"></param>
public void UnregisterEventHandlers(Assembly assembly)
{
foreach (var t in GetEventHandlersFromAssembly(assembly))
this.UnregisterEventHandler(t);
}
private static IEnumerable<Type> GetEventHandlersFromAssembly(Assembly assembly)
=> assembly.GetTypes()
.Where(t => t.GetCustomAttribute<EventHandlerAttribute>() is not null);
private void UnregisterEventHandlerImpl(object? handler, Type type, bool registerStatic = true)
{
if (!this._registrationToDelegate.TryGetValue((handler, type, registerStatic), out var delegateLists)
|| delegateLists.Count == 0)
return;
foreach (var (evnt, dlgt) in delegateLists[0])
evnt.RemoveEventHandler(this, dlgt);
delegateLists.RemoveAt(0);
if (delegateLists.Count == 0)
this._registrationToDelegate.Remove((handler, type, registerStatic));
}
private void RegisterEventHandlerImpl(object? handler, Type type, bool registerStatic = true)
{
var delegates = (
from method in type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
let attribute = method.GetCustomAttribute<EventAttribute>()
where attribute is not null && ((registerStatic && method.IsStatic) || handler is not null)
let eventName = attribute.EventName ?? method.Name
let eventInfo = this.GetType().GetEvent(eventName)
?? throw new ArgumentException($"Tried to register handler to non-existent event \"{eventName}\"")
let eventHandlerType = eventInfo.EventHandlerType
let dlgt = (method.IsStatic
? Delegate.CreateDelegate(eventHandlerType, method, false)
: Delegate.CreateDelegate(eventHandlerType, handler, method, false))
?? throw new ArgumentException($"Method \"{method}\" does not adhere to event specification \"{eventHandlerType}\"")
select (eventInfo, dlgt)
).ToArray();
this._registrationToDelegate[(handler, type, registerStatic)]
= this._registrationToDelegate.TryGetValue((handler, type, registerStatic), out var delList) ? delList : (delList = new());
delList.Add(delegates);
foreach (var (evnt, dlgt) in delegates)
evnt.AddEventHandler(this, dlgt);
}
}
diff --git a/DisCatSharp/Clients/DiscordClient.Events.cs b/DisCatSharp/Clients/DiscordClient.Events.cs
index 49e1729bd..773037149 100644
--- a/DisCatSharp/Clients/DiscordClient.Events.cs
+++ b/DisCatSharp/Clients/DiscordClient.Events.cs
@@ -1,1080 +1,1080 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using DisCatSharp.Common.Utilities;
using DisCatSharp.EventArgs;
using Microsoft.Extensions.Logging;
namespace DisCatSharp;
/// <summary>
/// Represents a discord client.
/// </summary>
public sealed partial class DiscordClient
{
/// <summary>
/// Gets the event execution limit.
/// </summary>
internal static TimeSpan EventExecutionLimit { get; } = TimeSpan.FromSeconds(1);
#region WebSocket
/// <summary>
/// Fired whenever a WebSocket error occurs within the client.
/// </summary>
public event AsyncEventHandler<DiscordClient, SocketErrorEventArgs> SocketErrored
{
add => this._socketErrored.Register(value);
remove => this._socketErrored.Unregister(value);
}
private AsyncEvent<DiscordClient, SocketErrorEventArgs> _socketErrored;
/// <summary>
/// Fired whenever WebSocket connection is established.
/// </summary>
public event AsyncEventHandler<DiscordClient, SocketEventArgs> SocketOpened
{
add => this._socketOpened.Register(value);
remove => this._socketOpened.Unregister(value);
}
private AsyncEvent<DiscordClient, SocketEventArgs> _socketOpened;
/// <summary>
/// Fired whenever WebSocket connection is terminated.
/// </summary>
public event AsyncEventHandler<DiscordClient, SocketCloseEventArgs> SocketClosed
{
add => this._socketClosed.Register(value);
remove => this._socketClosed.Unregister(value);
}
private AsyncEvent<DiscordClient, SocketCloseEventArgs> _socketClosed;
/// <summary>
/// Fired when the client enters ready state.
/// </summary>
public event AsyncEventHandler<DiscordClient, ReadyEventArgs> Ready
{
add => this._ready.Register(value);
remove => this._ready.Unregister(value);
}
private AsyncEvent<DiscordClient, ReadyEventArgs> _ready;
/// <summary>
/// Fired whenever a session is resumed.
/// </summary>
public event AsyncEventHandler<DiscordClient, ReadyEventArgs> Resumed
{
add => this._resumed.Register(value);
remove => this._resumed.Unregister(value);
}
private AsyncEvent<DiscordClient, ReadyEventArgs> _resumed;
/// <summary>
/// Fired on received heartbeat ACK.
/// </summary>
public event AsyncEventHandler<DiscordClient, HeartbeatEventArgs> Heartbeated
{
add => this._heartbeated.Register(value);
remove => this._heartbeated.Unregister(value);
}
private AsyncEvent<DiscordClient, HeartbeatEventArgs> _heartbeated;
#endregion
#region Channel
/// <summary>
/// Fired when a new channel is created.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, ChannelCreateEventArgs> ChannelCreated
{
add => this._channelCreated.Register(value);
remove => this._channelCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, ChannelCreateEventArgs> _channelCreated;
/// <summary>
/// Fired when a channel is updated.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, ChannelUpdateEventArgs> ChannelUpdated
{
add => this._channelUpdated.Register(value);
remove => this._channelUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, ChannelUpdateEventArgs> _channelUpdated;
/// <summary>
/// Fired when a channel is deleted
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, ChannelDeleteEventArgs> ChannelDeleted
{
add => this._channelDeleted.Register(value);
remove => this._channelDeleted.Unregister(value);
}
private AsyncEvent<DiscordClient, ChannelDeleteEventArgs> _channelDeleted;
/// <summary>
/// Fired when a dm channel is deleted
/// For this Event you need the <see cref="DiscordIntents.DirectMessages"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, DmChannelDeleteEventArgs> DmChannelDeleted
{
add => this._dmChannelDeleted.Register(value);
remove => this._dmChannelDeleted.Unregister(value);
}
private AsyncEvent<DiscordClient, DmChannelDeleteEventArgs> _dmChannelDeleted;
/// <summary>
/// Fired whenever a channel's pinned message list is updated.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, ChannelPinsUpdateEventArgs> ChannelPinsUpdated
{
add => this._channelPinsUpdated.Register(value);
remove => this._channelPinsUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, ChannelPinsUpdateEventArgs> _channelPinsUpdated;
#endregion
#region Guild
/// <summary>
/// Fired when the user joins a new guild.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
/// <remarks>[alias="GuildJoined"][alias="JoinedGuild"]</remarks>
public event AsyncEventHandler<DiscordClient, GuildCreateEventArgs> GuildCreated
{
add => this._guildCreated.Register(value);
remove => this._guildCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildCreateEventArgs> _guildCreated;
/// <summary>
/// Fired when a guild is becoming available.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildCreateEventArgs> GuildAvailable
{
add => this._guildAvailable.Register(value);
remove => this._guildAvailable.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildCreateEventArgs> _guildAvailable;
/// <summary>
/// Fired when a guild is updated.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildUpdateEventArgs> GuildUpdated
{
add => this._guildUpdated.Register(value);
remove => this._guildUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildUpdateEventArgs> _guildUpdated;
/// <summary>
/// Fired when the user leaves or is removed from a guild.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildDeleteEventArgs> GuildDeleted
{
add => this._guildDeleted.Register(value);
remove => this._guildDeleted.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildDeleteEventArgs> _guildDeleted;
/// <summary>
/// Fired when a guild becomes unavailable.
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildDeleteEventArgs> GuildUnavailable
{
add => this._guildUnavailable.Register(value);
remove => this._guildUnavailable.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildDeleteEventArgs> _guildUnavailable;
/// <summary>
/// Fired when all guilds finish streaming from Discord.
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildDownloadCompletedEventArgs> GuildDownloadCompleted
{
add => this._guildDownloadCompletedEv.Register(value);
remove => this._guildDownloadCompletedEv.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildDownloadCompletedEventArgs> _guildDownloadCompletedEv;
/// <summary>
/// Fired when a guilds emojis get updated
/// For this Event you need the <see cref="DiscordIntents.GuildEmojisAndStickers"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildEmojisUpdateEventArgs> GuildEmojisUpdated
{
add => this._guildEmojisUpdated.Register(value);
remove => this._guildEmojisUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildEmojisUpdateEventArgs> _guildEmojisUpdated;
/// <summary>
/// Fired when a guilds stickers get updated
/// For this Event you need the <see cref="DiscordIntents.GuildEmojisAndStickers"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildStickersUpdateEventArgs> GuildStickersUpdated
{
add => this._guildStickersUpdated.Register(value);
remove => this._guildStickersUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildStickersUpdateEventArgs> _guildStickersUpdated;
/// <summary>
/// Fired when a guild integration is updated.
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildIntegrationsUpdateEventArgs> GuildIntegrationsUpdated
{
add => this._guildIntegrationsUpdated.Register(value);
remove => this._guildIntegrationsUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildIntegrationsUpdateEventArgs> _guildIntegrationsUpdated;
/// <summary>
/// Fired when a guild audit log entry was created.
/// Requires bot to have the <see cref="Permissions.ViewAuditLog" /> permission.
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildAuditLogEntryCreateEventArgs> GuildAuditLogEntryCreated
{
add => this._guildAuditLogEntryCreated.Register(value);
remove => this._guildAuditLogEntryCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildAuditLogEntryCreateEventArgs> _guildAuditLogEntryCreated;
#endregion
#region Automod
/// <summary>
/// Fired when an auto mod rule gets created.
/// </summary>
public event AsyncEventHandler<DiscordClient, AutomodRuleCreateEventArgs> AutomodRuleCreated
{
add => this._automodRuleCreated.Register(value);
remove => this._automodRuleCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, AutomodRuleCreateEventArgs> _automodRuleCreated;
/// <summary>
/// Fired when an auto mod rule gets updated.
/// </summary>
public event AsyncEventHandler<DiscordClient, AutomodRuleUpdateEventArgs> AutomodRuleUpdated
{
add => this._automodRuleUpdated.Register(value);
remove => this._automodRuleUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, AutomodRuleUpdateEventArgs> _automodRuleUpdated;
/// <summary>
/// Fired when an auto mod rule gets deleted.
/// </summary>
public event AsyncEventHandler<DiscordClient, AutomodRuleDeleteEventArgs> AutomodRuleDeleted
{
add => this._automodRuleDeleted.Register(value);
remove => this._automodRuleDeleted.Unregister(value);
}
private AsyncEvent<DiscordClient, AutomodRuleDeleteEventArgs> _automodRuleDeleted;
/// <summary>
/// Fired when a rule is triggered and an action is executed.
/// </summary>
public event AsyncEventHandler<DiscordClient, AutomodActionExecutedEventArgs> AutomodActionExecuted
{
add => this._automodActionExecuted.Register(value);
remove => this._automodActionExecuted.Unregister(value);
}
private AsyncEvent<DiscordClient, AutomodActionExecutedEventArgs> _automodActionExecuted;
#endregion
#region Guild Ban
/// <summary>
/// Fired when a guild ban gets added
/// For this Event you need the <see cref="DiscordIntents.GuildBans"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildBanAddEventArgs> GuildBanAdded
{
add => this._guildBanAdded.Register(value);
remove => this._guildBanAdded.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildBanAddEventArgs> _guildBanAdded;
/// <summary>
/// Fired when a guild ban gets removed
/// For this Event you need the <see cref="DiscordIntents.GuildBans"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildBanRemoveEventArgs> GuildBanRemoved
{
add => this._guildBanRemoved.Register(value);
remove => this._guildBanRemoved.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildBanRemoveEventArgs> _guildBanRemoved;
#endregion
#region Guild Timeout
/// <summary>
/// Fired when a guild member timeout gets added.
/// For this Event you need the <see cref="DiscordIntents.GuildBans"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildMemberTimeoutAddEventArgs> GuildMemberTimeoutAdded
{
add => this._guildMemberTimeoutAdded.Register(value);
remove => this._guildMemberTimeoutAdded.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildMemberTimeoutAddEventArgs> _guildMemberTimeoutAdded;
/// <summary>
/// Fired when a guild member timeout gets changed.
/// For this Event you need the <see cref="DiscordIntents.GuildMembers"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildMemberTimeoutUpdateEventArgs> GuildMemberTimeoutChanged
{
add => this._guildMemberTimeoutChanged.Register(value);
remove => this._guildMemberTimeoutChanged.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildMemberTimeoutUpdateEventArgs> _guildMemberTimeoutChanged;
/// <summary>
/// Fired when a guild member timeout gets removed.
/// For this Event you need the <see cref="DiscordIntents.GuildMembers"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildMemberTimeoutRemoveEventArgs> GuildMemberTimeoutRemoved
{
add => this._guildMemberTimeoutRemoved.Register(value);
remove => this._guildMemberTimeoutRemoved.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildMemberTimeoutRemoveEventArgs> _guildMemberTimeoutRemoved;
#endregion
#region Guild Scheduled Event
/// <summary>
/// Fired when a scheduled Event is created.
/// For this Event you need the <see cref="DiscordIntents.GuildScheduledEvents"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildScheduledEventCreateEventArgs> GuildScheduledEventCreated
{
add => this._guildScheduledEventCreated.Register(value);
remove => this._guildScheduledEventCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildScheduledEventCreateEventArgs> _guildScheduledEventCreated;
/// <summary>
/// Fired when a scheduled Event is updated.
/// For this Event you need the <see cref="DiscordIntents.GuildScheduledEvents"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildScheduledEventUpdateEventArgs> GuildScheduledEventUpdated
{
add => this._guildScheduledEventUpdated.Register(value);
remove => this._guildScheduledEventUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildScheduledEventUpdateEventArgs> _guildScheduledEventUpdated;
/// <summary>
/// Fired when a scheduled Event is deleted.
/// For this Event you need the <see cref="DiscordIntents.GuildScheduledEvents"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildScheduledEventDeleteEventArgs> GuildScheduledEventDeleted
{
add => this._guildScheduledEventDeleted.Register(value);
remove => this._guildScheduledEventDeleted.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildScheduledEventDeleteEventArgs> _guildScheduledEventDeleted;
/// <summary>
/// Fired when a user subscribes to a scheduled event.
/// For this Event you need the <see cref="DiscordIntents.GuildScheduledEvents"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildScheduledEventUserAddEventArgs> GuildScheduledEventUserAdded
{
add => this._guildScheduledEventUserAdded.Register(value);
remove => this._guildScheduledEventUserAdded.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildScheduledEventUserAddEventArgs> _guildScheduledEventUserAdded;
/// <summary>
/// Fired when a user unsubscribes from a scheduled event.
/// For this Event you need the <see cref="DiscordIntents.GuildScheduledEvents"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildScheduledEventUserRemoveEventArgs> GuildScheduledEventUserRemoved
{
add => this._guildScheduledEventUserRemoved.Register(value);
remove => this._guildScheduledEventUserRemoved.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildScheduledEventUserRemoveEventArgs> _guildScheduledEventUserRemoved;
#endregion
#region Guild Integration
/// <summary>
/// Fired when a guild integration is created.
/// For this Event you need the <see cref="DiscordIntents.GuildIntegrations"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildIntegrationCreateEventArgs> GuildIntegrationCreated
{
add => this._guildIntegrationCreated.Register(value);
remove => this._guildIntegrationCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildIntegrationCreateEventArgs> _guildIntegrationCreated;
/// <summary>
/// Fired when a guild integration is updated.
/// For this Event you need the <see cref="DiscordIntents.GuildIntegrations"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildIntegrationUpdateEventArgs> GuildIntegrationUpdated
{
add => this._guildIntegrationUpdated.Register(value);
remove => this._guildIntegrationUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildIntegrationUpdateEventArgs> _guildIntegrationUpdated;
/// <summary>
/// Fired when a guild integration is deleted.
/// For this Event you need the <see cref="DiscordIntents.GuildIntegrations"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildIntegrationDeleteEventArgs> GuildIntegrationDeleted
{
add => this._guildIntegrationDeleted.Register(value);
remove => this._guildIntegrationDeleted.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildIntegrationDeleteEventArgs> _guildIntegrationDeleted;
#endregion
#region Guild Member
/// <summary>
/// Fired when a new user joins a guild.
/// For this Event you need the <see cref="DiscordIntents.GuildMembers"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildMemberAddEventArgs> GuildMemberAdded
{
add => this._guildMemberAdded.Register(value);
remove => this._guildMemberAdded.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildMemberAddEventArgs> _guildMemberAdded;
/// <summary>
/// Fired when a user is removed from a guild (leave/kick/ban).
/// For this Event you need the <see cref="DiscordIntents.GuildMembers"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildMemberRemoveEventArgs> GuildMemberRemoved
{
add => this._guildMemberRemoved.Register(value);
remove => this._guildMemberRemoved.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildMemberRemoveEventArgs> _guildMemberRemoved;
/// <summary>
/// Fired when a guild member is updated.
/// For this Event you need the <see cref="DiscordIntents.GuildMembers"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildMemberUpdateEventArgs> GuildMemberUpdated
{
add => this._guildMemberUpdated.Register(value);
remove => this._guildMemberUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildMemberUpdateEventArgs> _guildMemberUpdated;
/// <summary>
/// Fired in response to Gateway Request Guild Members.
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildMembersChunkEventArgs> GuildMembersChunked
{
add => this._guildMembersChunked.Register(value);
remove => this._guildMembersChunked.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildMembersChunkEventArgs> _guildMembersChunked;
#endregion
#region Guild Role
/// <summary>
/// Fired when a guild role is created.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildRoleCreateEventArgs> GuildRoleCreated
{
add => this._guildRoleCreated.Register(value);
remove => this._guildRoleCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildRoleCreateEventArgs> _guildRoleCreated;
/// <summary>
/// Fired when a guild role is updated.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildRoleUpdateEventArgs> GuildRoleUpdated
{
add => this._guildRoleUpdated.Register(value);
remove => this._guildRoleUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildRoleUpdateEventArgs> _guildRoleUpdated;
/// <summary>
/// Fired when a guild role is updated.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildRoleDeleteEventArgs> GuildRoleDeleted
{
add => this._guildRoleDeleted.Register(value);
remove => this._guildRoleDeleted.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildRoleDeleteEventArgs> _guildRoleDeleted;
#endregion
#region Invite
/// <summary>
/// Fired when an invite is created.
/// For this Event you need the <see cref="DiscordIntents.GuildInvites"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, InviteCreateEventArgs> InviteCreated
{
add => this._inviteCreated.Register(value);
remove => this._inviteCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, InviteCreateEventArgs> _inviteCreated;
/// <summary>
/// Fired when an invite is deleted.
/// For this Event you need the <see cref="DiscordIntents.GuildInvites"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, InviteDeleteEventArgs> InviteDeleted
{
add => this._inviteDeleted.Register(value);
remove => this._inviteDeleted.Unregister(value);
}
private AsyncEvent<DiscordClient, InviteDeleteEventArgs> _inviteDeleted;
#endregion
#region Message
/// <summary>
/// Fired when a message is created.
/// For this Event you need the <see cref="DiscordIntents.GuildMessages"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, MessageCreateEventArgs> MessageCreated
{
add => this._messageCreated.Register(value);
remove => this._messageCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, MessageCreateEventArgs> _messageCreated;
/// <summary>
/// Fired when message is acknowledged by the user.
/// For this Event you need the <see cref="DiscordIntents.GuildMessages"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, MessageAcknowledgeEventArgs> MessageAcknowledged
{
add => this._messageAcknowledged.Register(value);
remove => this._messageAcknowledged.Unregister(value);
}
private AsyncEvent<DiscordClient, MessageAcknowledgeEventArgs> _messageAcknowledged;
/// <summary>
/// Fired when a message is updated.
/// For this Event you need the <see cref="DiscordIntents.GuildMessages"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, MessageUpdateEventArgs> MessageUpdated
{
add => this._messageUpdated.Register(value);
remove => this._messageUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, MessageUpdateEventArgs> _messageUpdated;
/// <summary>
/// Fired when a message is deleted.
/// For this Event you need the <see cref="DiscordIntents.GuildMessages"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, MessageDeleteEventArgs> MessageDeleted
{
add => this._messageDeleted.Register(value);
remove => this._messageDeleted.Unregister(value);
}
private AsyncEvent<DiscordClient, MessageDeleteEventArgs> _messageDeleted;
/// <summary>
/// Fired when multiple messages are deleted at once.
/// For this Event you need the <see cref="DiscordIntents.GuildMessages"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, MessageBulkDeleteEventArgs> MessagesBulkDeleted
{
add => this._messagesBulkDeleted.Register(value);
remove => this._messagesBulkDeleted.Unregister(value);
}
private AsyncEvent<DiscordClient, MessageBulkDeleteEventArgs> _messagesBulkDeleted;
#endregion
#region Message Reaction
/// <summary>
/// Fired when a reaction gets added to a message.
/// For this Event you need the <see cref="DiscordIntents.GuildMessageReactions"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, MessageReactionAddEventArgs> MessageReactionAdded
{
add => this._messageReactionAdded.Register(value);
remove => this._messageReactionAdded.Unregister(value);
}
private AsyncEvent<DiscordClient, MessageReactionAddEventArgs> _messageReactionAdded;
/// <summary>
/// Fired when a reaction gets removed from a message.
/// For this Event you need the <see cref="DiscordIntents.GuildMessageReactions"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, MessageReactionRemoveEventArgs> MessageReactionRemoved
{
add => this._messageReactionRemoved.Register(value);
remove => this._messageReactionRemoved.Unregister(value);
}
private AsyncEvent<DiscordClient, MessageReactionRemoveEventArgs> _messageReactionRemoved;
/// <summary>
/// Fired when all reactions get removed from a message.
/// For this Event you need the <see cref="DiscordIntents.GuildMessageReactions"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, MessageReactionsClearEventArgs> MessageReactionsCleared
{
add => this._messageReactionsCleared.Register(value);
remove => this._messageReactionsCleared.Unregister(value);
}
private AsyncEvent<DiscordClient, MessageReactionsClearEventArgs> _messageReactionsCleared;
/// <summary>
/// Fired when all reactions of a specific reaction are removed from a message.
/// For this Event you need the <see cref="DiscordIntents.GuildMessageReactions"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, MessageReactionRemoveEmojiEventArgs> MessageReactionRemovedEmoji
{
add => this._messageReactionRemovedEmoji.Register(value);
remove => this._messageReactionRemovedEmoji.Unregister(value);
}
private AsyncEvent<DiscordClient, MessageReactionRemoveEmojiEventArgs> _messageReactionRemovedEmoji;
#endregion
#region Activities
/// <summary>
/// Fired when a embedded activity has been updated.
/// </summary>
public event AsyncEventHandler<DiscordClient, EmbeddedActivityUpdateEventArgs> EmbeddedActivityUpdated
{
add => this._embeddedActivityUpdated.Register(value);
remove => this._embeddedActivityUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, EmbeddedActivityUpdateEventArgs> _embeddedActivityUpdated;
#endregion
#region Presence/User Update
/// <summary>
/// Fired when a presence has been updated.
/// For this Event you need the <see cref="DiscordIntents.GuildPresences"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, PresenceUpdateEventArgs> PresenceUpdated
{
add => this._presenceUpdated.Register(value);
remove => this._presenceUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, PresenceUpdateEventArgs> _presenceUpdated;
/// <summary>
/// Fired when the current user updates their settings.
/// For this Event you need the <see cref="DiscordIntents.GuildPresences"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, UserSettingsUpdateEventArgs> UserSettingsUpdated
{
add => this._userSettingsUpdated.Register(value);
remove => this._userSettingsUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, UserSettingsUpdateEventArgs> _userSettingsUpdated;
/// <summary>
/// Fired when properties about the current user change.
/// </summary>
/// <remarks>
/// NB: This event only applies for changes to the <b>current user</b>, the client that is connected to Discord.
/// For this Event you need the <see cref="DiscordIntents.GuildPresences"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </remarks>
public event AsyncEventHandler<DiscordClient, UserUpdateEventArgs> UserUpdated
{
add => this._userUpdated.Register(value);
remove => this._userUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, UserUpdateEventArgs> _userUpdated;
#endregion
#region Stage Instance
/// <summary>
/// Fired when a Stage Instance is created.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, StageInstanceCreateEventArgs> StageInstanceCreated
{
add => this._stageInstanceCreated.Register(value);
remove => this._stageInstanceCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, StageInstanceCreateEventArgs> _stageInstanceCreated;
/// <summary>
/// Fired when a Stage Instance is updated.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, StageInstanceUpdateEventArgs> StageInstanceUpdated
{
add => this._stageInstanceUpdated.Register(value);
remove => this._stageInstanceUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, StageInstanceUpdateEventArgs> _stageInstanceUpdated;
/// <summary>
/// Fired when a Stage Instance is deleted.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, StageInstanceDeleteEventArgs> StageInstanceDeleted
{
add => this._stageInstanceDeleted.Register(value);
remove => this._stageInstanceDeleted.Unregister(value);
}
private AsyncEvent<DiscordClient, StageInstanceDeleteEventArgs> _stageInstanceDeleted;
#endregion
#region Thread
/// <summary>
/// Fired when a thread is created.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, ThreadCreateEventArgs> ThreadCreated
{
add => this._threadCreated.Register(value);
remove => this._threadCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, ThreadCreateEventArgs> _threadCreated;
/// <summary>
/// Fired when a thread is updated.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, ThreadUpdateEventArgs> ThreadUpdated
{
add => this._threadUpdated.Register(value);
remove => this._threadUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, ThreadUpdateEventArgs> _threadUpdated;
/// <summary>
/// Fired when a thread is deleted.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, ThreadDeleteEventArgs> ThreadDeleted
{
add => this._threadDeleted.Register(value);
remove => this._threadDeleted.Unregister(value);
}
private AsyncEvent<DiscordClient, ThreadDeleteEventArgs> _threadDeleted;
/// <summary>
/// Fired when a thread member is updated.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, ThreadListSyncEventArgs> ThreadListSynced
{
add => this._threadListSynced.Register(value);
remove => this._threadListSynced.Unregister(value);
}
private AsyncEvent<DiscordClient, ThreadListSyncEventArgs> _threadListSynced;
/// <summary>
/// Fired when a thread member is updated.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, ThreadMemberUpdateEventArgs> ThreadMemberUpdated
{
add => this._threadMemberUpdated.Register(value);
remove => this._threadMemberUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, ThreadMemberUpdateEventArgs> _threadMemberUpdated;
/// <summary>
/// Fired when the thread members are updated.
/// For this Event you need the <see cref="DiscordIntents.GuildMembers"/> or <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, ThreadMembersUpdateEventArgs> ThreadMembersUpdated
{
add => this._threadMembersUpdated.Register(value);
remove => this._threadMembersUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, ThreadMembersUpdateEventArgs> _threadMembersUpdated;
#endregion
#region Voice
/// <summary>
/// Fired when someone joins/leaves/moves voice channels.
/// For this Event you need the <see cref="DiscordIntents.GuildVoiceStates"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, VoiceStateUpdateEventArgs> VoiceStateUpdated
{
add => this._voiceStateUpdated.Register(value);
remove => this._voiceStateUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, VoiceStateUpdateEventArgs> _voiceStateUpdated;
/// <summary>
/// Fired when a guild's voice server is updated.
/// For this Event you need the <see cref="DiscordIntents.GuildVoiceStates"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, VoiceServerUpdateEventArgs> VoiceServerUpdated
{
add => this._voiceServerUpdated.Register(value);
remove => this._voiceServerUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, VoiceServerUpdateEventArgs> _voiceServerUpdated;
#endregion
#region Application
/// <summary>
/// Fired when a new application command is registered.
/// </summary>
public event AsyncEventHandler<DiscordClient, ApplicationCommandEventArgs> ApplicationCommandCreated
{
add => this._applicationCommandCreated.Register(value);
remove => this._applicationCommandCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, ApplicationCommandEventArgs> _applicationCommandCreated;
/// <summary>
/// Fired when an application command is updated.
/// </summary>
public event AsyncEventHandler<DiscordClient, ApplicationCommandEventArgs> ApplicationCommandUpdated
{
add => this._applicationCommandUpdated.Register(value);
remove => this._applicationCommandUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, ApplicationCommandEventArgs> _applicationCommandUpdated;
/// <summary>
/// Fired when an application command is deleted.
/// </summary>
public event AsyncEventHandler<DiscordClient, ApplicationCommandEventArgs> ApplicationCommandDeleted
{
add => this._applicationCommandDeleted.Register(value);
remove => this._applicationCommandDeleted.Unregister(value);
}
private AsyncEvent<DiscordClient, ApplicationCommandEventArgs> _applicationCommandDeleted;
/// <summary>
/// Fired when a new application command is registered.
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildApplicationCommandCountEventArgs> GuildApplicationCommandCountUpdated
{
add => this._guildApplicationCommandCountUpdated.Register(value);
remove => this._guildApplicationCommandCountUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildApplicationCommandCountEventArgs> _guildApplicationCommandCountUpdated;
/// <summary>
/// Fired when a user uses a context menu.
/// </summary>
public event AsyncEventHandler<DiscordClient, ContextMenuInteractionCreateEventArgs> ContextMenuInteractionCreated
{
add => this._contextMenuInteractionCreated.Register(value);
remove => this._contextMenuInteractionCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, ContextMenuInteractionCreateEventArgs> _contextMenuInteractionCreated;
/// <summary>
/// Fired when application command permissions gets updated.
/// </summary>
public event AsyncEventHandler<DiscordClient, ApplicationCommandPermissionsUpdateEventArgs> ApplicationCommandPermissionsUpdated
{
add => this._applicationCommandPermissionsUpdated.Register(value);
remove => this._applicationCommandPermissionsUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, ApplicationCommandPermissionsUpdateEventArgs> _applicationCommandPermissionsUpdated;
#endregion
#region Misc
/// <summary>
/// Fired when an interaction is invoked.
/// </summary>
public event AsyncEventHandler<DiscordClient, InteractionCreateEventArgs> InteractionCreated
{
add => this._interactionCreated.Register(value);
remove => this._interactionCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, InteractionCreateEventArgs> _interactionCreated;
/// <summary>
/// Fired when a component is invoked.
/// </summary>
public event AsyncEventHandler<DiscordClient, ComponentInteractionCreateEventArgs> ComponentInteractionCreated
{
add => this._componentInteractionCreated.Register(value);
remove => this._componentInteractionCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, ComponentInteractionCreateEventArgs> _componentInteractionCreated;
/// <summary>
/// Fired when a user starts typing in a channel.
/// </summary>
public event AsyncEventHandler<DiscordClient, TypingStartEventArgs> TypingStarted
{
add => this._typingStarted.Register(value);
remove => this._typingStarted.Unregister(value);
}
private AsyncEvent<DiscordClient, TypingStartEventArgs> _typingStarted;
/// <summary>
/// Fired when an unknown event gets received.
/// </summary>
public event AsyncEventHandler<DiscordClient, UnknownEventArgs> UnknownEvent
{
add => this._unknownEvent.Register(value);
remove => this._unknownEvent.Unregister(value);
}
private AsyncEvent<DiscordClient, UnknownEventArgs> _unknownEvent;
/// <summary>
/// Fired whenever webhooks update.
/// </summary>
public event AsyncEventHandler<DiscordClient, WebhooksUpdateEventArgs> WebhooksUpdated
{
add => this._webhooksUpdated.Register(value);
remove => this._webhooksUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, WebhooksUpdateEventArgs> _webhooksUpdated;
/// <summary>
/// Fired whenever an error occurs within an event handler.
/// </summary>
public event AsyncEventHandler<DiscordClient, ClientErrorEventArgs> ClientErrored
{
add => this._clientErrored.Register(value);
remove => this._clientErrored.Unregister(value);
}
private AsyncEvent<DiscordClient, ClientErrorEventArgs> _clientErrored;
#endregion
#region Error Handling
/// <summary>
/// Handles event errors.
/// </summary>
/// <param name="asyncEvent">The event.</param>
/// <param name="ex">The exception.</param>
/// <param name="handler">The event handler.</param>
/// <param name="sender">The sender.</param>
/// <param name="eventArgs">The event args.</param>
internal void EventErrorHandler<TSender, TArgs>(AsyncEvent<TSender, TArgs> asyncEvent, Exception ex, AsyncEventHandler<TSender, TArgs> handler, TSender sender, TArgs eventArgs)
where TArgs : AsyncEventArgs
{
if (ex is AsyncEventTimeoutException)
{
this.Logger.LogWarning(LoggerEvents.EventHandlerException, $"An event handler for {asyncEvent.Name} took too long to execute. Defined as \"{handler.Method.ToString().Replace(handler.Method.ReturnType.ToString(), "").TrimStart()}\" located in \"{handler.Method.DeclaringType}\".");
return;
}
this.Logger.LogError(LoggerEvents.EventHandlerException, ex, "Event handler exception for event {0} thrown from {1} (defined in {2})", asyncEvent.Name, handler.Method, handler.Method.DeclaringType);
this._clientErrored.InvokeAsync(this, new ClientErrorEventArgs(this.ServiceProvider) { EventName = asyncEvent.Name, Exception = ex }).ConfigureAwait(false).GetAwaiter().GetResult();
}
/// <summary>
/// Fired when a ratelimit was hit.
/// </summary>
public event AsyncEventHandler<DiscordClient, RateLimitExceptionEventArgs> RateLimitHit
{
add => this._rateLimitHit.Register(value);
remove => this._rateLimitHit.Unregister(value);
}
internal AsyncEvent<DiscordClient, RateLimitExceptionEventArgs> _rateLimitHit;
/// <summary>
/// Fired on heartbeat attempt cancellation due to too many failed heartbeats.
/// </summary>
public event AsyncEventHandler<DiscordClient, ZombiedEventArgs> Zombied
{
add => this._zombied.Register(value);
remove => this._zombied.Unregister(value);
}
private AsyncEvent<DiscordClient, ZombiedEventArgs> _zombied;
/// <summary>
/// Fired when a gateway payload is received.
/// </summary>
public event AsyncEventHandler<DiscordClient, PayloadReceivedEventArgs> PayloadReceived
{
add => this._payloadReceived.Register(value);
remove => this._payloadReceived.Unregister(value);
}
private AsyncEvent<DiscordClient, PayloadReceivedEventArgs> _payloadReceived;
/// <summary>
/// Handles event handler exceptions.
/// </summary>
/// <param name="asyncEvent">The event.</param>
/// <param name="ex">The exception.</param>
/// <param name="handler">The event handler.</param>
/// <param name="sender">The sender.</param>
/// <param name="eventArgs">The event args.</param>
private void Goof<TSender, TArgs>(AsyncEvent<TSender, TArgs> asyncEvent, Exception ex, AsyncEventHandler<TSender, TArgs> handler, TSender sender, TArgs eventArgs)
where TArgs : AsyncEventArgs => this.Logger.LogCritical(LoggerEvents.EventHandlerException, ex, "Exception event handler {0} (defined in {1}) threw an exception", handler.Method, handler.Method.DeclaringType);
#endregion
}
diff --git a/DisCatSharp/Clients/DiscordClient.WebSocket.cs b/DisCatSharp/Clients/DiscordClient.WebSocket.cs
index a00de8f47..0c16772ae 100644
--- a/DisCatSharp/Clients/DiscordClient.WebSocket.cs
+++ b/DisCatSharp/Clients/DiscordClient.WebSocket.cs
@@ -1,589 +1,589 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using DisCatSharp.EventArgs;
using DisCatSharp.Net.Abstractions;
using DisCatSharp.Net.WebSocket;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace DisCatSharp;
/// <summary>
/// Represents a discord websocket client.
/// </summary>
public sealed partial class DiscordClient
{
#region Private Fields
private int _heartbeatInterval;
private DateTimeOffset _lastHeartbeat;
private Task _heartbeatTask;
internal static DateTimeOffset DiscordEpoch = new(2015, 1, 1, 0, 0, 0, TimeSpan.Zero);
private int _skippedHeartbeats;
private long _lastSequence;
internal IWebSocketClient WebSocketClient;
private PayloadDecompressor _payloadDecompressor;
private CancellationTokenSource _cancelTokenSource;
private CancellationToken _cancelToken;
#endregion
#region Connection Semaphore
/// <summary>
/// Gets the socket locks.
/// </summary>
private static ConcurrentDictionary<ulong, SocketLock> s_socketLocks { get; } = new();
/// <summary>
/// Gets the session lock.
/// </summary>
private readonly ManualResetEventSlim _sessionLock = new(true);
#endregion
#region Internal Connection Methods
/// <summary>
/// Reconnects the websocket client.
/// </summary>
/// <param name="startNewSession">Whether to start a new session.</param>
/// <param name="code">The reconnect code.</param>
/// <param name="message">The reconnect message.</param>
private Task InternalReconnectAsync(bool startNewSession = false, int code = 1000, string message = "")
{
if (startNewSession)
this._sessionId = null;
_ = this.WebSocketClient.DisconnectAsync(code, message);
return Task.CompletedTask;
}
/// <summary>
/// Connects the websocket client.
/// </summary>
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.PresencesInternal[this.CurrentUser.Id] = new DiscordPresence
{
Discord = this,
RawActivity = new TransportActivity(),
Activity = new DiscordActivity(),
Status = UserStatus.Online,
InternalUser = new UserWithIdOnly()
{
Id = this.CurrentUser.Id
}
};
}
else
{
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._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;
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");
this.Logger.LogDebug(LoggerEvents.Startup, "Connecting to {gw}", this.GatewayUri.AbsoluteUri);
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)
{
using var ms = new MemoryStream();
if (!this._payloadDecompressor.TryDecompress(new ArraySegment<byte>(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.ActivityInternal, this._status.Status, Utilities.GetDateTimeOffsetFromMilliseconds(this._status.IdleSince.Value)).ConfigureAwait(false);
else
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)
/// <summary>
/// Handles the socket message.
/// </summary>
/// <param name="data">The data.</param>
internal async Task HandleSocketMessageAsync(string data)
{
var payload = JsonConvert.DeserializeObject<GatewayPayload>(data);
this._lastSequence = payload.Sequence ?? this._lastSequence;
switch (payload.OpCode)
{
case GatewayOpCode.Dispatch:
await Task.Run(async () => 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<GatewayHello>()).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;
}
}
/// <summary>
/// Handles the heartbeat.
/// </summary>
/// <param name="seq">The sequence.</param>
internal async Task OnHeartbeatAsync(long seq)
{
this.Logger.LogTrace(LoggerEvents.WebSocketReceive, "Received HEARTBEAT (OP1)");
await this.SendHeartbeatAsync(seq).ConfigureAwait(false);
}
/// <summary>
/// Handles the reconnect event.
/// </summary>
internal async Task OnReconnectAsync()
{
this.Logger.LogTrace(LoggerEvents.WebSocketReceive, "Received RECONNECT (OP7)");
await this.InternalReconnectAsync(code: 4000, message: "OP7 acknowledged").ConfigureAwait(false);
}
/// <summary>
/// Handles the invalidate session event
/// </summary>
/// <param name="data">Unknown. Please fill documentation.</param>
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);
}
}
/// <summary>
/// Handles the hello event.
/// </summary>
/// <param name="hello">The gateway hello payload.</param>
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);
}
/// <summary>
/// Handles the heartbeat acknowledge event.
/// </summary>
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);
}
/// <summary>
/// Handles the heartbeat loop.
/// </summary>
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
/// <summary>
/// Updates the status.
/// </summary>
/// <param name="activity">The activity.</param>
/// <param name="userStatus">The optional user status.</param>
/// <param name="idleSince">Since when is the client performing the specified activity.</param>
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 sinceUnix = idleSince != null ? (long?)Utilities.GetUnixTime(idleSince.Value) : null;
var act = activity ?? new DiscordActivity();
var status = new StatusUpdate
{
Activity = new TransportActivity(act),
IdleSince = sinceUnix,
IsAfk = idleSince != null,
Status = userStatus ?? UserStatus.Online
};
// Solution to have status persist between sessions
this._status = status;
var statusUpdate = new GatewayPayload
{
OpCode = GatewayOpCode.StatusUpdate,
Data = status
};
var statusstr = JsonConvert.SerializeObject(statusUpdate);
await this.WsSendAsync(statusstr).ConfigureAwait(false);
if (!this.PresencesInternal.ContainsKey(this.CurrentUser.Id))
{
this.PresencesInternal[this.CurrentUser.Id] = new DiscordPresence
{
Discord = this,
Activity = act,
Status = userStatus ?? UserStatus.Online,
InternalUser = new UserWithIdOnly { Id = this.CurrentUser.Id }
};
}
else
{
var pr = this.PresencesInternal[this.CurrentUser.Id];
pr.Activity = act;
pr.Status = userStatus ?? pr.Status;
}
}
/// <summary>
/// Sends the heartbeat.
/// </summary>
/// <param name="seq">The sequenze.</param>
internal async Task SendHeartbeatAsync(long seq)
{
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 (!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 heartbeatStr = JsonConvert.SerializeObject(heartbeat);
await this.WsSendAsync(heartbeatStr).ConfigureAwait(false);
this._lastHeartbeat = DateTimeOffset.Now;
Interlocked.Increment(ref this._skippedHeartbeats);
}
/// <summary>
/// Sends the identify payload.
/// </summary>
/// <param name="status">The status update payload.</param>
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);
}
/// <summary>
/// Sends the resume payload.
/// </summary>
internal async Task SendResumeAsync()
{
var resume = new GatewayResume
{
Token = Utilities.GetFormattedToken(this),
SessionId = this._sessionId,
SequenceNumber = Volatile.Read(ref this._lastSequence)
};
var resumePayload = new GatewayPayload
{
OpCode = GatewayOpCode.Resume,
Data = resume
};
var resumestr = JsonConvert.SerializeObject(resumePayload);
this.GatewayUri = new Uri(this._resumeGatewayUrl);
this.Logger.LogDebug(LoggerEvents.ConnectionClose, "Request to resume via {gw}", this.GatewayUri.AbsoluteUri);
await this.WsSendAsync(resumestr).ConfigureAwait(false);
}
/// <summary>
/// Internals the update gateway async.
/// </summary>
/// <returns>A Task.</returns>
internal async Task InternalUpdateGatewayAsync()
{
var info = await this.GetGatewayInfoAsync().ConfigureAwait(false);
this.GatewayInfo = info;
this.GatewayUri = new Uri(info.Url);
}
/// <summary>
/// Sends a websocket message.
/// </summary>
/// <param name="payload">The payload to send.</param>
internal async Task WsSendAsync(string payload)
{
this.Logger.LogTrace(LoggerEvents.GatewayWsTx, payload);
await this.WebSocketClient.SendMessageAsync(payload).ConfigureAwait(false);
}
#endregion
#region Semaphore Methods
/// <summary>
/// Gets the socket lock.
/// </summary>
/// <returns>The added socket lock.</returns>
private SocketLock GetSocketLock()
=> 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 c9185f670..7d539f1b5 100644
--- a/DisCatSharp/Clients/DiscordClient.cs
+++ b/DisCatSharp/Clients/DiscordClient.cs
@@ -1,1592 +1,1592 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// A Discord API wrapper.
/// </summary>
public sealed partial class DiscordClient : BaseDiscordClient
{
#region Internal Fields/Properties
internal bool IsShard = false;
/// <summary>
/// Gets the message cache.
/// </summary>
internal RingBuffer<DiscordMessage> MessageCache { get; }
private List<BaseExtension> _extensions = new();
private StatusUpdate _status;
/// <summary>
/// Gets the connection lock.
/// </summary>
private readonly ManualResetEventSlim _connectionLock = new(true);
#endregion
#region Public Fields/Properties
/// <summary>
/// Gets the gateway protocol version.
/// </summary>
public int GatewayVersion { get; internal set; }
/// <summary>
/// Gets the gateway session information for this client.
/// </summary>
public GatewayInfo GatewayInfo { get; internal set; }
/// <summary>
/// Gets the gateway URL.
/// </summary>
public Uri GatewayUri { get; internal set; }
/// <summary>
/// Gets the total number of shards the bot is connected to.
/// </summary>
public int ShardCount => this.GatewayInfo != null
? this.GatewayInfo.ShardCount
: this.Configuration.ShardCount;
/// <summary>
/// Gets the currently connected shard ID.
/// </summary>
public int ShardId
=> this.Configuration.ShardId;
/// <summary>
/// Gets the intents configured for this client.
/// </summary>
public DiscordIntents Intents
=> this.Configuration.Intents;
/// <summary>
/// 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
/// <see cref="GuildAvailable"/> or <see cref="GuildDownloadCompleted"/> events haven't been fired yet)
/// </summary>
public override IReadOnlyDictionary<ulong, DiscordGuild> Guilds { get; }
internal ConcurrentDictionary<ulong, DiscordGuild> GuildsInternal = new();
/// <summary>
/// Gets the websocket latency for this client.
/// </summary>
public int Ping
=> Volatile.Read(ref this._ping);
private int _ping;
/// <summary>
/// Gets the collection of presences held by this client.
/// </summary>
public IReadOnlyDictionary<ulong, DiscordPresence> Presences
=> this._presencesLazy.Value;
internal Dictionary<ulong, DiscordPresence> PresencesInternal = new();
private Lazy<IReadOnlyDictionary<ulong, DiscordPresence>> _presencesLazy;
/// <summary>
/// Gets the collection of presences held by this client.
/// </summary>
public IReadOnlyDictionary<string, DiscordActivity> EmbeddedActivities
=> this._embeddedActivitiesLazy.Value;
internal Dictionary<string, DiscordActivity> EmbeddedActivitiesInternal = new();
private Lazy<IReadOnlyDictionary<string, DiscordActivity>> _embeddedActivitiesLazy;
#endregion
#region Constructor/Internal Setup
/// <summary>
/// Initializes a new instance of <see cref="DiscordClient"/>.
/// </summary>
/// <param name="config">Specifies configuration parameters.</param>
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<DiscordMessage>(this.Configuration.MessageCacheSize)
: null;
}
this.InternalSetup();
this.Guilds = new ReadOnlyConcurrentDictionary<ulong, DiscordGuild>(this.GuildsInternal);
}
/// <summary>
/// Internal setup of the Client.
/// </summary>
internal void InternalSetup()
{
this._clientErrored = new AsyncEvent<DiscordClient, ClientErrorEventArgs>("CLIENT_ERRORED", EventExecutionLimit, this.Goof);
this._socketErrored = new AsyncEvent<DiscordClient, SocketErrorEventArgs>("SOCKET_ERRORED", EventExecutionLimit, this.Goof);
this._socketOpened = new AsyncEvent<DiscordClient, SocketEventArgs>("SOCKET_OPENED", EventExecutionLimit, this.EventErrorHandler);
this._socketClosed = new AsyncEvent<DiscordClient, SocketCloseEventArgs>("SOCKET_CLOSED", EventExecutionLimit, this.EventErrorHandler);
this._ready = new AsyncEvent<DiscordClient, ReadyEventArgs>("READY", EventExecutionLimit, this.EventErrorHandler);
this._resumed = new AsyncEvent<DiscordClient, ReadyEventArgs>("RESUMED", EventExecutionLimit, this.EventErrorHandler);
this._channelCreated = new AsyncEvent<DiscordClient, ChannelCreateEventArgs>("CHANNEL_CREATED", EventExecutionLimit, this.EventErrorHandler);
this._channelUpdated = new AsyncEvent<DiscordClient, ChannelUpdateEventArgs>("CHANNEL_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._channelDeleted = new AsyncEvent<DiscordClient, ChannelDeleteEventArgs>("CHANNEL_DELETED", EventExecutionLimit, this.EventErrorHandler);
this._dmChannelDeleted = new AsyncEvent<DiscordClient, DmChannelDeleteEventArgs>("DM_CHANNEL_DELETED", EventExecutionLimit, this.EventErrorHandler);
this._channelPinsUpdated = new AsyncEvent<DiscordClient, ChannelPinsUpdateEventArgs>("CHANNEL_PINS_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._guildCreated = new AsyncEvent<DiscordClient, GuildCreateEventArgs>("GUILD_CREATED", EventExecutionLimit, this.EventErrorHandler);
this._guildAvailable = new AsyncEvent<DiscordClient, GuildCreateEventArgs>("GUILD_AVAILABLE", EventExecutionLimit, this.EventErrorHandler);
this._guildUpdated = new AsyncEvent<DiscordClient, GuildUpdateEventArgs>("GUILD_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._guildDeleted = new AsyncEvent<DiscordClient, GuildDeleteEventArgs>("GUILD_DELETED", EventExecutionLimit, this.EventErrorHandler);
this._guildUnavailable = new AsyncEvent<DiscordClient, GuildDeleteEventArgs>("GUILD_UNAVAILABLE", EventExecutionLimit, this.EventErrorHandler);
this._guildDownloadCompletedEv = new AsyncEvent<DiscordClient, GuildDownloadCompletedEventArgs>("GUILD_DOWNLOAD_COMPLETED", EventExecutionLimit, this.EventErrorHandler);
this._inviteCreated = new AsyncEvent<DiscordClient, InviteCreateEventArgs>("INVITE_CREATED", EventExecutionLimit, this.EventErrorHandler);
this._inviteDeleted = new AsyncEvent<DiscordClient, InviteDeleteEventArgs>("INVITE_DELETED", EventExecutionLimit, this.EventErrorHandler);
this._messageCreated = new AsyncEvent<DiscordClient, MessageCreateEventArgs>("MESSAGE_CREATED", EventExecutionLimit, this.EventErrorHandler);
this._presenceUpdated = new AsyncEvent<DiscordClient, PresenceUpdateEventArgs>("PRESENCE_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._guildBanAdded = new AsyncEvent<DiscordClient, GuildBanAddEventArgs>("GUILD_BAN_ADD", EventExecutionLimit, this.EventErrorHandler);
this._guildBanRemoved = new AsyncEvent<DiscordClient, GuildBanRemoveEventArgs>("GUILD_BAN_REMOVED", EventExecutionLimit, this.EventErrorHandler);
this._guildEmojisUpdated = new AsyncEvent<DiscordClient, GuildEmojisUpdateEventArgs>("GUILD_EMOJI_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._guildStickersUpdated = new AsyncEvent<DiscordClient, GuildStickersUpdateEventArgs>("GUILD_STICKER_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._guildIntegrationsUpdated = new AsyncEvent<DiscordClient, GuildIntegrationsUpdateEventArgs>("GUILD_INTEGRATIONS_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._guildMemberAdded = new AsyncEvent<DiscordClient, GuildMemberAddEventArgs>("GUILD_MEMBER_ADD", EventExecutionLimit, this.EventErrorHandler);
this._guildMemberRemoved = new AsyncEvent<DiscordClient, GuildMemberRemoveEventArgs>("GUILD_MEMBER_REMOVED", EventExecutionLimit, this.EventErrorHandler);
this._guildMemberUpdated = new AsyncEvent<DiscordClient, GuildMemberUpdateEventArgs>("GUILD_MEMBER_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._guildRoleCreated = new AsyncEvent<DiscordClient, GuildRoleCreateEventArgs>("GUILD_ROLE_CREATED", EventExecutionLimit, this.EventErrorHandler);
this._guildRoleUpdated = new AsyncEvent<DiscordClient, GuildRoleUpdateEventArgs>("GUILD_ROLE_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._guildRoleDeleted = new AsyncEvent<DiscordClient, GuildRoleDeleteEventArgs>("GUILD_ROLE_DELETED", EventExecutionLimit, this.EventErrorHandler);
this._messageAcknowledged = new AsyncEvent<DiscordClient, MessageAcknowledgeEventArgs>("MESSAGE_ACKNOWLEDGED", EventExecutionLimit, this.EventErrorHandler);
this._messageUpdated = new AsyncEvent<DiscordClient, MessageUpdateEventArgs>("MESSAGE_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._messageDeleted = new AsyncEvent<DiscordClient, MessageDeleteEventArgs>("MESSAGE_DELETED", EventExecutionLimit, this.EventErrorHandler);
this._messagesBulkDeleted = new AsyncEvent<DiscordClient, MessageBulkDeleteEventArgs>("MESSAGE_BULK_DELETED", EventExecutionLimit, this.EventErrorHandler);
this._interactionCreated = new AsyncEvent<DiscordClient, InteractionCreateEventArgs>("INTERACTION_CREATED", EventExecutionLimit, this.EventErrorHandler);
this._componentInteractionCreated = new AsyncEvent<DiscordClient, ComponentInteractionCreateEventArgs>("COMPONENT_INTERACTED", EventExecutionLimit, this.EventErrorHandler);
this._contextMenuInteractionCreated = new AsyncEvent<DiscordClient, ContextMenuInteractionCreateEventArgs>("CONTEXT_MENU_INTERACTED", EventExecutionLimit, this.EventErrorHandler);
this._typingStarted = new AsyncEvent<DiscordClient, TypingStartEventArgs>("TYPING_STARTED", EventExecutionLimit, this.EventErrorHandler);
this._userSettingsUpdated = new AsyncEvent<DiscordClient, UserSettingsUpdateEventArgs>("USER_SETTINGS_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._userUpdated = new AsyncEvent<DiscordClient, UserUpdateEventArgs>("USER_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._voiceStateUpdated = new AsyncEvent<DiscordClient, VoiceStateUpdateEventArgs>("VOICE_STATE_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._voiceServerUpdated = new AsyncEvent<DiscordClient, VoiceServerUpdateEventArgs>("VOICE_SERVER_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._guildMembersChunked = new AsyncEvent<DiscordClient, GuildMembersChunkEventArgs>("GUILD_MEMBERS_CHUNKED", EventExecutionLimit, this.EventErrorHandler);
this._unknownEvent = new AsyncEvent<DiscordClient, UnknownEventArgs>("UNKNOWN_EVENT", EventExecutionLimit, this.EventErrorHandler);
this._messageReactionAdded = new AsyncEvent<DiscordClient, MessageReactionAddEventArgs>("MESSAGE_REACTION_ADDED", EventExecutionLimit, this.EventErrorHandler);
this._messageReactionRemoved = new AsyncEvent<DiscordClient, MessageReactionRemoveEventArgs>("MESSAGE_REACTION_REMOVED", EventExecutionLimit, this.EventErrorHandler);
this._messageReactionsCleared = new AsyncEvent<DiscordClient, MessageReactionsClearEventArgs>("MESSAGE_REACTIONS_CLEARED", EventExecutionLimit, this.EventErrorHandler);
this._messageReactionRemovedEmoji = new AsyncEvent<DiscordClient, MessageReactionRemoveEmojiEventArgs>("MESSAGE_REACTION_REMOVED_EMOJI", EventExecutionLimit, this.EventErrorHandler);
this._webhooksUpdated = new AsyncEvent<DiscordClient, WebhooksUpdateEventArgs>("WEBHOOKS_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._heartbeated = new AsyncEvent<DiscordClient, HeartbeatEventArgs>("HEARTBEATED", EventExecutionLimit, this.EventErrorHandler);
this._applicationCommandCreated = new AsyncEvent<DiscordClient, ApplicationCommandEventArgs>("APPLICATION_COMMAND_CREATED", EventExecutionLimit, this.EventErrorHandler);
this._applicationCommandUpdated = new AsyncEvent<DiscordClient, ApplicationCommandEventArgs>("APPLICATION_COMMAND_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._applicationCommandDeleted = new AsyncEvent<DiscordClient, ApplicationCommandEventArgs>("APPLICATION_COMMAND_DELETED", EventExecutionLimit, this.EventErrorHandler);
this._guildApplicationCommandCountUpdated = new AsyncEvent<DiscordClient, GuildApplicationCommandCountEventArgs>("GUILD_APPLICATION_COMMAND_COUNTS_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._applicationCommandPermissionsUpdated = new AsyncEvent<DiscordClient, ApplicationCommandPermissionsUpdateEventArgs>("APPLICATION_COMMAND_PERMISSIONS_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._guildIntegrationCreated = new AsyncEvent<DiscordClient, GuildIntegrationCreateEventArgs>("INTEGRATION_CREATED", EventExecutionLimit, this.EventErrorHandler);
this._guildIntegrationUpdated = new AsyncEvent<DiscordClient, GuildIntegrationUpdateEventArgs>("INTEGRATION_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._guildIntegrationDeleted = new AsyncEvent<DiscordClient, GuildIntegrationDeleteEventArgs>("INTEGRATION_DELETED", EventExecutionLimit, this.EventErrorHandler);
this._stageInstanceCreated = new AsyncEvent<DiscordClient, StageInstanceCreateEventArgs>("STAGE_INSTANCE_CREATED", EventExecutionLimit, this.EventErrorHandler);
this._stageInstanceUpdated = new AsyncEvent<DiscordClient, StageInstanceUpdateEventArgs>("STAGE_INSTANCE_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._stageInstanceDeleted = new AsyncEvent<DiscordClient, StageInstanceDeleteEventArgs>("STAGE_INSTANCE_DELETED", EventExecutionLimit, this.EventErrorHandler);
this._threadCreated = new AsyncEvent<DiscordClient, ThreadCreateEventArgs>("THREAD_CREATED", EventExecutionLimit, this.EventErrorHandler);
this._threadUpdated = new AsyncEvent<DiscordClient, ThreadUpdateEventArgs>("THREAD_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._threadDeleted = new AsyncEvent<DiscordClient, ThreadDeleteEventArgs>("THREAD_DELETED", EventExecutionLimit, this.EventErrorHandler);
this._threadListSynced = new AsyncEvent<DiscordClient, ThreadListSyncEventArgs>("THREAD_LIST_SYNCED", EventExecutionLimit, this.EventErrorHandler);
this._threadMemberUpdated = new AsyncEvent<DiscordClient, ThreadMemberUpdateEventArgs>("THREAD_MEMBER_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._threadMembersUpdated = new AsyncEvent<DiscordClient, ThreadMembersUpdateEventArgs>("THREAD_MEMBERS_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._zombied = new AsyncEvent<DiscordClient, ZombiedEventArgs>("ZOMBIED", EventExecutionLimit, this.EventErrorHandler);
this._payloadReceived = new AsyncEvent<DiscordClient, PayloadReceivedEventArgs>("PAYLOAD_RECEIVED", EventExecutionLimit, this.EventErrorHandler);
this._guildScheduledEventCreated = new AsyncEvent<DiscordClient, GuildScheduledEventCreateEventArgs>("GUILD_SCHEDULED_EVENT_CREATED", EventExecutionLimit, this.EventErrorHandler);
this._guildScheduledEventUpdated = new AsyncEvent<DiscordClient, GuildScheduledEventUpdateEventArgs>("GUILD_SCHEDULED_EVENT_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._guildScheduledEventDeleted = new AsyncEvent<DiscordClient, GuildScheduledEventDeleteEventArgs>("GUILD_SCHEDULED_EVENT_DELETED", EventExecutionLimit, this.EventErrorHandler);
this._guildScheduledEventUserAdded = new AsyncEvent<DiscordClient, GuildScheduledEventUserAddEventArgs>("GUILD_SCHEDULED_EVENT_USER_ADDED", EventExecutionLimit, this.EventErrorHandler);
this._guildScheduledEventUserRemoved = new AsyncEvent<DiscordClient, GuildScheduledEventUserRemoveEventArgs>("GUILD_SCHEDULED_EVENT_USER_REMOVED", EventExecutionLimit, this.EventErrorHandler);
this._embeddedActivityUpdated = new AsyncEvent<DiscordClient, EmbeddedActivityUpdateEventArgs>("EMBEDDED_ACTIVITY_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._guildMemberTimeoutAdded = new AsyncEvent<DiscordClient, GuildMemberTimeoutAddEventArgs>("GUILD_MEMBER_TIMEOUT_ADDED", EventExecutionLimit, this.EventErrorHandler);
this._guildMemberTimeoutChanged = new AsyncEvent<DiscordClient, GuildMemberTimeoutUpdateEventArgs>("GUILD_MEMBER_TIMEOUT_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._guildMemberTimeoutRemoved = new AsyncEvent<DiscordClient, GuildMemberTimeoutRemoveEventArgs>("GUILD_MEMBER_TIMEOUT_REMOVED", EventExecutionLimit, this.EventErrorHandler);
this._rateLimitHit = new AsyncEvent<DiscordClient, RateLimitExceptionEventArgs>("RATELIMIT_HIT", EventExecutionLimit, this.EventErrorHandler);
this._automodRuleCreated = new AsyncEvent<DiscordClient, AutomodRuleCreateEventArgs>("AUTO_MODERATION_RULE_CREATED", EventExecutionLimit, this.EventErrorHandler);
this._automodRuleUpdated = new AsyncEvent<DiscordClient, AutomodRuleUpdateEventArgs>("AUTO_MODERATION_RULE_UPDATED", EventExecutionLimit, this.EventErrorHandler);
this._automodRuleDeleted = new AsyncEvent<DiscordClient, AutomodRuleDeleteEventArgs>("AUTO_MODERATION_RULE_DELETED", EventExecutionLimit, this.EventErrorHandler);
this._automodActionExecuted = new AsyncEvent<DiscordClient, AutomodActionExecutedEventArgs>("AUTO_MODERATION_ACTION_EXECUTED", EventExecutionLimit, this.EventErrorHandler);
this._guildAuditLogEntryCreated = new AsyncEvent<DiscordClient, GuildAuditLogEntryCreateEventArgs>("GUILD_AUDIT_LOG_ENTRY_CREATED", EventExecutionLimit, this.EventErrorHandler);
this.GuildsInternal.Clear();
this._presencesLazy = new Lazy<IReadOnlyDictionary<ulong, DiscordPresence>>(() => new ReadOnlyDictionary<ulong, DiscordPresence>(this.PresencesInternal));
this._embeddedActivitiesLazy = new Lazy<IReadOnlyDictionary<string, DiscordActivity>>(() => new ReadOnlyDictionary<string, DiscordActivity>(this.EmbeddedActivitiesInternal));
}
#endregion
#region Client Extension Methods
/// <summary>
/// Registers an extension with this client.
/// </summary>
/// <param name="ext">Extension to register.</param>
public void AddExtension(BaseExtension ext)
{
ext.Setup(this);
this._extensions.Add(ext);
}
/// <summary>
/// Retrieves a previously registered extension from this client.
/// </summary>
/// <typeparam name="T">The type of extension to retrieve.</typeparam>
/// <returns>The requested extension.</returns>
public T GetExtension<T>() where T : BaseExtension
=> this._extensions.FirstOrDefault(x => x.GetType() == typeof(T)) as T;
#endregion
#region Public Connection Methods
/// <summary>
/// Connects to the gateway.
/// </summary>
/// <param name="activity">The activity to set. Defaults to null.</param>
/// <param name="status">The optional status to set. Defaults to null.</param>
/// <param name="idlesince">Since when is the client performing the specified activity. Defaults to null.</param>
/// <exception cref="UnauthorizedException">Thrown when an invalid token was provided.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task ConnectAsync(DiscordActivity activity = null, UserStatus? status = null, DateTimeOffset? idlesince = null)
{
// Check if connection lock is already set, and set it if it isn't
if (!this._connectionLock.Wait(0))
throw new InvalidOperationException("This client is already connected.");
this._connectionLock.Set();
var w = 7500;
var i = 5;
var s = false;
Exception cex = null;
if (activity == null && status == null && idlesince == null)
this._status = null;
else
{
var sinceUnix = idlesince != null ? (long?)Utilities.GetUnixTime(idlesince.Value) : null;
this._status = new StatusUpdate()
{
Activity = new TransportActivity(activity),
Status = status ?? UserStatus.Online,
IdleSince = sinceUnix,
IsAfk = idlesince != null,
ActivityInternal = activity
};
}
if (!this.IsShard)
{
if (this.Configuration.TokenType != TokenType.Bot)
this.Logger.LogWarning(LoggerEvents.Misc, "You are logging in with a token that is not a bot token. This is not officially supported by Discord, and can result in your account being terminated if you aren't careful.");
this.Logger.LogInformation(LoggerEvents.Startup, "Lib {0}, version {1}", this.BotLibrary, this.VersionString);
}
while (i-- > 0 || this.Configuration.ReconnectIndefinitely)
{
try
{
await this.InternalConnectAsync().ConfigureAwait(false);
s = true;
break;
}
catch (UnauthorizedException e)
{
FailConnection(this._connectionLock);
throw new Exception("Authentication failed. Check your token and try again.", e);
}
catch (PlatformNotSupportedException)
{
FailConnection(this._connectionLock);
throw;
}
catch (NotImplementedException)
{
FailConnection(this._connectionLock);
throw;
}
catch (Exception ex)
{
FailConnection(null);
cex = ex;
if (i <= 0 && !this.Configuration.ReconnectIndefinitely) break;
this.Logger.LogError(LoggerEvents.ConnectionFailure, ex, "Connection attempt failed, retrying in {0}s", w / 1000);
await Task.Delay(w).ConfigureAwait(false);
if (i > 0)
w *= 2;
}
}
if (!s && cex != null)
{
this._connectionLock.Set();
throw new Exception("Could not connect to Discord.", cex);
}
// non-closure, hence args
static void FailConnection(ManualResetEventSlim cl) =>
// unlock this (if applicable) so we can let others attempt to connect
cl?.Set();
}
/// <summary>
/// Reconnects to the gateway.
/// </summary>
/// <param name="startNewSession">Whether to start a new session.</param>
public Task ReconnectAsync(bool startNewSession = true)
=> this.InternalReconnectAsync(startNewSession, code: startNewSession ? 1000 : 4002);
/// <summary>
/// Disconnects from the gateway.
/// </summary>
public async Task DisconnectAsync()
{
this.Configuration.AutoReconnect = false;
if (this.WebSocketClient != null)
await this.WebSocketClient.DisconnectAsync().ConfigureAwait(false);
}
#endregion
#region Public REST Methods
/// <summary>
/// Gets a user.
/// </summary>
/// <param name="userId">Id of the user</param>
/// <param name="fetch">Whether to ignore the cache. Defaults to false.</param>
/// <returns>The requested user.</returns>
/// <exception cref="NotFoundException">Thrown when the user does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordUser> GetUserAsync(ulong userId, bool fetch = false)
{
if (!fetch)
{
return this.TryGetCachedUserInternal(userId, out var usr) ? usr : new DiscordUser { Id = userId, Discord = this };
}
else
{
var usr = await this.ApiClient.GetUserAsync(userId).ConfigureAwait(false);
usr = this.UserCache.AddOrUpdate(userId, usr, (id, old) =>
{
old.Username = usr.Username;
old.Discriminator = usr.Discriminator;
old.AvatarHash = usr.AvatarHash;
old.BannerHash = usr.BannerHash;
old.BannerColorInternal = usr.BannerColorInternal;
old.AvatarDecorationHash = usr.AvatarDecorationHash;
old.ThemeColorsInternal = usr.ThemeColorsInternal;
old.Pronouns = usr.Pronouns;
return old;
});
return usr;
}
}
/// <summary>
/// Gets a applications rpc information.
/// </summary>
/// <param name="applicationId">Id of the application</param>
/// <returns>The requested application.</returns>
/// <exception cref="NotFoundException">Thrown when the application does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordRpcApplication> GetRpcApplicationAsync(ulong applicationId)
=> await this.ApiClient.GetApplicationInfoAsync(applicationId);
/// <summary>
/// Tries to get a user.
/// </summary>
/// <param name="userId">Id of the user.</param>
/// <param name="fetch">Whether to ignore the cache. Defaults to true.</param>
/// <returns>The requested user or null if not found.</returns>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public async Task<DiscordUser?> TryGetUserAsync(ulong userId, bool fetch = true)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
try
{
return await this.GetUserAsync(userId, fetch).ConfigureAwait(false);
}
catch (NotFoundException)
{
return null;
}
}
/// <summary>
/// Gets the applications role connection metadata.
/// </summary>
/// <returns>A list of metadata records or <see langword="null"/>.</returns>
public async Task<IReadOnlyList<DiscordApplicationRoleConnectionMetadata>> GetRoleConnectionMetadata()
=> await this.ApiClient.GetRoleConnectionMetadataRecords(this.CurrentApplication.Id);
/// <summary>
/// Updates the applications role connection metadata.
/// </summary>
/// <param name="metadata">A list of metadata objects. Max 5.</param>
public async Task<IReadOnlyList<DiscordApplicationRoleConnectionMetadata>> UpdateRoleConnectionMetadata(IEnumerable<DiscordApplicationRoleConnectionMetadata> metadata)
=> await this.ApiClient.UpdateRoleConnectionMetadataRecords(this.CurrentApplication.Id, metadata);
/// <summary>
/// Removes all global application commands.
/// </summary>
public async Task RemoveGlobalApplicationCommandsAsync()
=> await this.ApiClient.BulkOverwriteGlobalApplicationCommandsAsync(this.CurrentApplication.Id, Array.Empty<DiscordApplicationCommand>());
/// <summary>
/// Removes all global application commands for a specific guild id.
/// </summary>
/// <param name="guildId">The target guild id.</param>
public async Task RemoveGuildApplicationCommandsAsync(ulong guildId)
=> await this.ApiClient.BulkOverwriteGuildApplicationCommandsAsync(this.CurrentApplication.Id, guildId, Array.Empty<DiscordApplicationCommand>());
/// <summary>
/// Removes all global application commands for a specific guild.
/// </summary>
/// <param name="guild">The target guild.</param>
public async Task RemoveGuildApplicationCommandsAsync(DiscordGuild guild)
=> await this.RemoveGuildApplicationCommandsAsync(guild.Id);
/// <summary>
/// Gets a channel.
/// </summary>
/// <param name="id">The id of the channel to get.</param>
/// <param name="fetch">Whether to ignore the cache. Defaults to false.</param>
/// <returns>The requested channel.</returns>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordChannel> GetChannelAsync(ulong id, bool fetch = false)
=> (fetch ? null : this.InternalGetCachedChannel(id)) ?? await this.ApiClient.GetChannelAsync(id).ConfigureAwait(false);
/// <summary>
/// Tries to get a channel.
/// </summary>
/// <param name="id">The id of the channel to get.</param>
/// <param name="fetch">Whether to ignore the cache. Defaults to true.</param>
/// <returns>The requested channel or null if not found.</returns>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public async Task<DiscordChannel?> TryGetChannelAsync(ulong id, bool fetch = true)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
try
{
return await this.GetChannelAsync(id, fetch).ConfigureAwait(false);
}
catch (NotFoundException)
{
return null;
}
}
/// <summary>
/// Gets a thread.
/// </summary>
/// <param name="id">The id of the thread to get.</param>
/// <param name="fetch">Whether to ignore the cache. Defaults to false.</param>
/// <returns>The requested thread.</returns>
/// <exception cref="NotFoundException">Thrown when the thread does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordThreadChannel> GetThreadAsync(ulong id, bool fetch = false)
=> (fetch ? null : this.InternalGetCachedThread(id)) ?? await this.ApiClient.GetThreadAsync(id).ConfigureAwait(false);
/// <summary>
/// Tries to get a thread.
/// </summary>
/// <param name="id">The id of the thread to get.</param>
/// <param name="fetch">Whether to ignore the cache. Defaults to true.</param>
/// <returns>The requested thread or null if not found.</returns>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public async Task<DiscordThreadChannel?> TryGetThreadAsync(ulong id, bool fetch = true)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
try
{
return await this.GetThreadAsync(id, fetch).ConfigureAwait(false);
}
catch (NotFoundException)
{
return null;
}
}
/// <summary>
/// Sends a normal message.
/// </summary>
/// <param name="channel">The channel to send to.</param>
/// <param name="content">The message content to send.</param>
/// <returns>The message that was sent.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.SendMessages"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordMessage> SendMessageAsync(DiscordChannel channel, string content)
=> this.ApiClient.CreateMessageAsync(channel.Id, content, embeds: null, sticker: null, replyMessageId: null, mentionReply: false, failOnInvalidReply: false);
/// <summary>
/// Sends a message with an embed.
/// </summary>
/// <param name="channel">The channel to send to.</param>
/// <param name="embed">The embed to attach to the message.</param>
/// <returns>The message that was sent.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.SendMessages"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordMessage> SendMessageAsync(DiscordChannel channel, DiscordEmbed embed)
=> this.ApiClient.CreateMessageAsync(channel.Id, null, embed != null ? new[] { embed } : null, sticker: null, replyMessageId: null, mentionReply: false, failOnInvalidReply: false);
/// <summary>
/// Sends a message with content and an embed.
/// </summary>
/// <param name="channel">Channel to send to.</param>
/// <param name="content">The message content to send.</param>
/// <param name="embed">The embed to attach to the message.</param>
/// <returns>The message that was sent.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.SendMessages"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordMessage> 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);
/// <summary>
/// Sends a message with the <see cref="DisCatSharp.Entities.DiscordMessageBuilder"/>.
/// </summary>
/// <param name="channel">The channel to send the message to.</param>
/// <param name="builder">The message builder.</param>
/// <returns>The message that was sent.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.SendMessages"/> permission if TTS is false and <see cref="Permissions.SendTtsMessages"/> if TTS is true.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordMessage> SendMessageAsync(DiscordChannel channel, DiscordMessageBuilder builder)
=> this.ApiClient.CreateMessageAsync(channel.Id, builder);
/// <summary>
/// Sends a message with an <see cref="Action{DiscordMessageBuilder}"/>.
/// </summary>
/// <param name="channel">The channel to send the message to.</param>
/// <param name="action">The message builder.</param>
/// <returns>The message that was sent.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.SendMessages"/> permission if TTS is false and <see cref="Permissions.SendTtsMessages"/> if TTS is true.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordMessage> SendMessageAsync(DiscordChannel channel, Action<DiscordMessageBuilder> action)
{
var builder = new DiscordMessageBuilder();
action(builder);
return this.ApiClient.CreateMessageAsync(channel.Id, builder);
}
/// <summary>
/// Creates a guild. This requires the bot to be in less than 10 guilds total.
/// </summary>
/// <param name="name">Name of the guild.</param>
/// <param name="region">Voice region of the guild.</param>
/// <param name="icon">Stream containing the icon for the guild.</param>
/// <param name="verificationLevel">Verification level for the guild.</param>
/// <param name="defaultMessageNotifications">Default message notification settings for the guild.</param>
/// <param name="systemChannelFlags">System channel flags for the guild.</param>
/// <returns>The created guild.</returns>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordGuild> CreateGuildAsync(string name, string region = null, Optional<Stream> icon = default, VerificationLevel? verificationLevel = null,
DefaultMessageNotifications? defaultMessageNotifications = null, SystemChannelFlags? systemChannelFlags = null)
{
var iconb64 = ImageTool.Base64FromStream(icon);
return this.ApiClient.CreateGuildAsync(name, region, iconb64, verificationLevel, defaultMessageNotifications, systemChannelFlags);
}
/// <summary>
/// Creates a guild from a template. This requires the bot to be in less than 10 guilds total.
/// </summary>
/// <param name="code">The template code.</param>
/// <param name="name">Name of the guild.</param>
/// <param name="icon">Stream containing the icon for the guild.</param>
/// <returns>The created guild.</returns>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordGuild> CreateGuildFromTemplateAsync(string code, string name, Optional<Stream> icon = default)
{
var iconb64 = ImageTool.Base64FromStream(icon);
return this.ApiClient.CreateGuildFromTemplateAsync(code, name, iconb64);
}
/// <summary>
/// Gets a guild.
/// <para>Setting <paramref name="withCounts"/> to true will make a REST request.</para>
/// </summary>
/// <param name="id">The guild ID to search for.</param>
/// <param name="withCounts">Whether to include approximate presence and member counts in the returned guild.</param>
/// <param name="fetch">Whether to ignore the cache. Defaults to false.</param>
/// <returns>The requested Guild.</returns>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordGuild> GetGuildAsync(ulong id, bool? withCounts = null, bool fetch = false)
{
if (!fetch && this.GuildsInternal.TryGetValue(id, out var guild) && (!withCounts.HasValue || !withCounts.Value))
return guild;
guild = await this.ApiClient.GetGuildAsync(id, withCounts).ConfigureAwait(false);
var channels = await this.ApiClient.GetGuildChannelsAsync(guild.Id).ConfigureAwait(false);
foreach (var channel in channels) guild.ChannelsInternal[channel.Id] = channel;
return guild;
}
/// <summary>
/// Tries to get a guild.
/// <para>Setting <paramref name="withCounts"/> to true will make a REST request.</para>
/// </summary>
/// <param name="id">The guild ID to search for.</param>
/// <param name="withCounts">Whether to include approximate presence and member counts in the returned guild.</param>
/// <param name="fetch">Whether to ignore the cache. Defaults to true.</param>
/// <returns>The requested Guild or null if not found.</returns>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public async Task<DiscordGuild?> TryGetGuildAsync(ulong id, bool? withCounts = null, bool fetch = true)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
try
{
return await this.GetGuildAsync(id, withCounts, fetch).ConfigureAwait(false);
}
catch (NotFoundException)
{
return null;
}
}
/// <summary>
/// Gets a guild preview.
/// </summary>
/// <param name="id">The guild ID.</param>
/// <returns>A preview of the requested guild.</returns>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordGuildPreview> GetGuildPreviewAsync(ulong id)
=> this.ApiClient.GetGuildPreviewAsync(id);
/// <summary>
/// Tries to get a guild preview.
/// </summary>
/// <param name="id">The guild ID.</param>
/// <returns>A preview of the requested guild or null if not found.</returns>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public async Task<DiscordGuildPreview?> TryGetGuildPreviewAsync(ulong id)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
try
{
return await this.ApiClient.GetGuildPreviewAsync(id).ConfigureAwait(false);
}
catch (NotFoundException)
{
return null;
}
}
/// <summary>
/// Gets a guild widget.
/// </summary>
/// <param name="id">The Guild Id.</param>
/// <returns>A guild widget.</returns>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordWidget> GetGuildWidgetAsync(ulong id)
=> this.ApiClient.GetGuildWidgetAsync(id);
/// <summary>
/// Tries to get a guild widget.
/// </summary>
/// <param name="id">The Guild Id.</param>
/// <returns>The requested guild widget or null if not found.</returns>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public async Task<DiscordWidget?> TryGetGuildWidgetAsync(ulong id)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
try
{
return await this.ApiClient.GetGuildWidgetAsync(id);
}
catch (NotFoundException)
{
return null;
}
}
/// <summary>
/// Gets an invite.
/// </summary>
/// <param name="code">The invite code.</param>
/// <param name="withCounts">Whether to include presence and total member counts in the returned invite.</param>
/// <param name="withExpiration">Whether to include the expiration date in the returned invite.</param>
/// <param name="scheduledEventId">The scheduled event id.</param>
/// <returns>The requested invite.</returns>
/// <exception cref="NotFoundException">Thrown when the invite does not exists.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordInvite> GetInviteByCodeAsync(string code, bool? withCounts = null, bool? withExpiration = null, ulong? scheduledEventId = null)
=> this.ApiClient.GetInviteAsync(code, withCounts, withExpiration, scheduledEventId);
/// <summary>
/// Tries to get an invite.
/// </summary>
/// <param name="code">The invite code.</param>
/// <param name="withCounts">Whether to include presence and total member counts in the returned invite.</param>
/// <param name="withExpiration">Whether to include the expiration date in the returned invite.</param>
/// <param name="scheduledEventId">The scheduled event id.</param>
/// <returns>The requested invite or null if not found.</returns>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public async Task<DiscordInvite?> TryGetInviteByCodeAsync(string code, bool? withCounts = null, bool? withExpiration = null, ulong? scheduledEventId = null)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
try
{
return await this.GetInviteByCodeAsync(code, withCounts, withExpiration, scheduledEventId).ConfigureAwait(false);
}
catch (NotFoundException)
{
return null;
}
}
/// <summary>
/// Gets a list of user connections.
/// </summary>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<IReadOnlyList<DiscordConnection>> GetConnectionsAsync()
=> this.ApiClient.GetUserConnectionsAsync();
/// <summary>
/// Gets a sticker.
/// </summary>
/// <returns>The requested sticker.</returns>
/// <param name="id">The id of the sticker.</param>
/// <exception cref="NotFoundException">Thrown when the sticker does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordSticker> GetStickerAsync(ulong id)
=> this.ApiClient.GetStickerAsync(id);
/// <summary>
/// Tries to get a sticker.
/// </summary>
/// <returns>The requested sticker or null if not found.</returns>
/// <param name="id">The id of the sticker.</param>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public async Task<DiscordSticker?> TryGetStickerAsync(ulong id)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
try
{
return await this.GetStickerAsync(id).ConfigureAwait(false);
}
catch (NotFoundException)
{
return null;
}
}
/// <summary>
/// Gets all nitro sticker packs.
/// </summary>
/// <returns>List of sticker packs.</returns>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<IReadOnlyList<DiscordStickerPack>> GetStickerPacksAsync()
=> this.ApiClient.GetStickerPacksAsync();
/// <summary>
/// Gets the In-App OAuth Url.
/// </summary>
/// <param name="scopes">Defaults to <see cref="DisCatSharp.Enums.OAuthScopes.BOT_DEFAULT"/>.</param>
/// <param name="redir">Redirect Uri.</param>
/// <param name="permissions">Defaults to <see cref="Permissions.None"/>.</param>
/// <returns>The OAuth Url</returns>
public Uri GetInAppOAuth(Permissions permissions = Permissions.None, OAuthScopes scopes = OAuthScopes.BOT_DEFAULT, string redir = null)
{
permissions &= PermissionMethods.FullPerms;
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());
}
/// <summary>
/// Generates an In-App OAuth Url.
/// </summary>
/// <param name="bot">The bot to generate the url for.</param>
/// <param name="scopes">Defaults to <see cref="DisCatSharp.Enums.OAuthScopes.BOT_DEFAULT"/>.</param>
/// <param name="redir">Redirect Uri.</param>
/// <param name="permissions">Defaults to <see cref="Permissions.None"/>.</param>
/// <returns>The OAuth Url</returns>
public Uri GenerateInAppOauthFor(DiscordUser bot, Permissions permissions = Permissions.None, OAuthScopes scopes = OAuthScopes.BOT_DEFAULT, string redir = null)
{
if (!bot.IsBot)
throw new ArgumentException("The user must be a bot.", nameof(bot));
permissions &= PermissionMethods.FullPerms;
return new Uri(new QueryUriBuilder($"{DiscordDomain.GetDomain(CoreDomain.Discord).Url}{Endpoints.OAUTH2}{Endpoints.AUTHORIZE}")
.AddParameter("client_id", bot.Id.ToString(CultureInfo.InvariantCulture))
.AddParameter("scope", OAuth.ResolveScopes(scopes))
.AddParameter("permissions", ((long)permissions).ToString(CultureInfo.InvariantCulture))
.AddParameter("state", "")
.AddParameter("redirect_uri", redir ?? "")
.ToString());
}
/// <summary>
/// Gets a webhook.
/// </summary>
/// <param name="id">The target webhook id.</param>
/// <returns>The requested webhook.</returns>
/// <exception cref="NotFoundException">Thrown when the webhook does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordWebhook> GetWebhookAsync(ulong id)
=> this.ApiClient.GetWebhookAsync(id);
/// <summary>
/// Tries to get a webhook.
/// </summary>
/// <param name="id">The target webhook id.</param>
/// <returns>The requested webhook or null if not found.</returns>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public async Task<DiscordWebhook?> TryGetWebhookAsync(ulong id)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
try
{
return await this.GetWebhookAsync(id).ConfigureAwait(false);
}
catch (NotFoundException)
{
return null;
}
}
/// <summary>
/// Gets a webhook with a token.
/// </summary>
/// <param name="id">The target webhook id.</param>
/// <param name="token">The target webhook token.</param>
/// <returns>The requested webhook.</returns>
/// <exception cref="NotFoundException">Thrown when the webhook does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordWebhook> GetWebhookWithTokenAsync(ulong id, string token)
=> this.ApiClient.GetWebhookWithTokenAsync(id, token);
/// <summary>
/// Tries to get a webhook with a token.
/// </summary>
/// <param name="id">The target webhook id.</param>
/// <param name="token">The target webhook token.</param>
/// <returns>The requested webhook or null if not found.</returns>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public async Task<DiscordWebhook?> TryGetWebhookWithTokenAsync(ulong id, string token)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
try
{
return await this.GetWebhookWithTokenAsync(id, token).ConfigureAwait(false);
}
catch (NotFoundException)
{
return null;
}
}
/// <summary>
/// Updates current user's activity and status.
/// </summary>
/// <param name="activity">Activity to set.</param>
/// <param name="userStatus">Status of the user.</param>
/// <param name="idleSince">Since when is the client performing the specified activity.</param>
/// <returns></returns>
public Task UpdateStatusAsync(DiscordActivity activity = null, UserStatus? userStatus = null, DateTimeOffset? idleSince = null)
=> this.InternalUpdateStatusAsync(activity, userStatus, idleSince);
/// <summary>
/// Edits current user.
/// </summary>
/// <param name="username">New username.</param>
/// <param name="avatar">New avatar.</param>
/// <returns>The modified user.</returns>
/// <exception cref="NotFoundException">Thrown when the user does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordUser> UpdateCurrentUserAsync(string username = null, Optional<Stream> avatar = default)
{
var av64 = ImageTool.Base64FromStream(avatar);
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;
}
/// <summary>
/// Gets a guild template by the code.
/// </summary>
/// <param name="code">The code of the template.</param>
/// <returns>The guild template for the code.</returns>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordGuildTemplate> GetTemplateAsync(string code)
=> this.ApiClient.GetTemplateAsync(code);
/// <summary>
/// Gets all the global application commands for this application.
/// </summary>
/// <param name="withLocalizations">Whether to get the full localization dict.</param>
/// <returns>A list of global application commands.</returns>
public Task<IReadOnlyList<DiscordApplicationCommand>> GetGlobalApplicationCommandsAsync(bool withLocalizations = false) =>
this.ApiClient.GetGlobalApplicationCommandsAsync(this.CurrentApplication.Id, withLocalizations);
/// <summary>
/// Overwrites the existing global application commands. New commands are automatically created and missing commands are automatically deleted.
/// </summary>
/// <param name="commands">The list of commands to overwrite with.</param>
/// <returns>The list of global commands.</returns>
public Task<IReadOnlyList<DiscordApplicationCommand>> BulkOverwriteGlobalApplicationCommandsAsync(IEnumerable<DiscordApplicationCommand> commands) =>
this.ApiClient.BulkOverwriteGlobalApplicationCommandsAsync(this.CurrentApplication.Id, commands);
/// <summary>
/// Creates or overwrites a global application command.
/// </summary>
/// <param name="command">The command to create.</param>
/// <returns>The created command.</returns>
public Task<DiscordApplicationCommand> CreateGlobalApplicationCommandAsync(DiscordApplicationCommand command) =>
this.ApiClient.CreateGlobalApplicationCommandAsync(this.CurrentApplication.Id, command);
/// <summary>
/// Gets a global application command by its id.
/// </summary>
/// <param name="commandId">The id of the command to get.</param>
/// <returns>The command with the id.</returns>
public Task<DiscordApplicationCommand> GetGlobalApplicationCommandAsync(ulong commandId) =>
this.ApiClient.GetGlobalApplicationCommandAsync(this.CurrentApplication.Id, commandId);
/// <summary>
/// Edits a global application command.
/// </summary>
/// <param name="commandId">The id of the command to edit.</param>
/// <param name="action">Action to perform.</param>
/// <returns>The edited command.</returns>
public async Task<DiscordApplicationCommand> EditGlobalApplicationCommandAsync(ulong commandId, Action<ApplicationCommandEditModel> 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.NameLocalizations, mdl.DescriptionLocalizations, mdl.DefaultMemberPermissions, mdl.DmPermission, mdl.IsNsfw).ConfigureAwait(false);
}
/// <summary>
/// Deletes a global application command.
/// </summary>
/// <param name="commandId">The id of the command to delete.</param>
public Task DeleteGlobalApplicationCommandAsync(ulong commandId) =>
this.ApiClient.DeleteGlobalApplicationCommandAsync(this.CurrentApplication.Id, commandId);
/// <summary>
/// Gets all the application commands for a guild.
/// </summary>
/// <param name="guildId">The id of the guild to get application commands for.</param>
/// <param name="withLocalizations">Whether to get the full localization dict.</param>
/// <returns>A list of application commands in the guild.</returns>
public Task<IReadOnlyList<DiscordApplicationCommand>> GetGuildApplicationCommandsAsync(ulong guildId, bool withLocalizations = false) =>
this.ApiClient.GetGuildApplicationCommandsAsync(this.CurrentApplication.Id, guildId, withLocalizations);
/// <summary>
/// Overwrites the existing application commands in a guild. New commands are automatically created and missing commands are automatically deleted.
/// </summary>
/// <param name="guildId">The id of the guild.</param>
/// <param name="commands">The list of commands to overwrite with.</param>
/// <returns>The list of guild commands.</returns>
public Task<IReadOnlyList<DiscordApplicationCommand>> BulkOverwriteGuildApplicationCommandsAsync(ulong guildId, IEnumerable<DiscordApplicationCommand> commands) =>
this.ApiClient.BulkOverwriteGuildApplicationCommandsAsync(this.CurrentApplication.Id, guildId, commands);
/// <summary>
/// Creates or overwrites a guild application command.
/// </summary>
/// <param name="guildId">The id of the guild to create the application command in.</param>
/// <param name="command">The command to create.</param>
/// <returns>The created command.</returns>
public Task<DiscordApplicationCommand> CreateGuildApplicationCommandAsync(ulong guildId, DiscordApplicationCommand command) =>
this.ApiClient.CreateGuildApplicationCommandAsync(this.CurrentApplication.Id, guildId, command);
/// <summary>
/// Gets a application command in a guild by its id.
/// </summary>
/// <param name="guildId">The id of the guild the application command is in.</param>
/// <param name="commandId">The id of the command to get.</param>
/// <returns>The command with the id.</returns>
public Task<DiscordApplicationCommand> GetGuildApplicationCommandAsync(ulong guildId, ulong commandId) =>
this.ApiClient.GetGuildApplicationCommandAsync(this.CurrentApplication.Id, guildId, commandId);
/// <summary>
/// Edits a application command in a guild.
/// </summary>
/// <param name="guildId">The id of the guild the application command is in.</param>
/// <param name="commandId">The id of the command to edit.</param>
/// <param name="action">Action to perform.</param>
/// <returns>The edited command.</returns>
public async Task<DiscordApplicationCommand> EditGuildApplicationCommandAsync(ulong guildId, ulong commandId, Action<ApplicationCommandEditModel> 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.NameLocalizations, mdl.DescriptionLocalizations, mdl.DefaultMemberPermissions, mdl.DmPermission, mdl.IsNsfw).ConfigureAwait(false);
}
/// <summary>
/// Deletes a application command in a guild.
/// </summary>
/// <param name="guildId">The id of the guild to delete the application command in.</param>
/// <param name="commandId">The id of the command.</param>
public Task DeleteGuildApplicationCommandAsync(ulong guildId, ulong commandId) =>
this.ApiClient.DeleteGuildApplicationCommandAsync(this.CurrentApplication.Id, guildId, commandId);
/// <summary>
/// Gets all command permissions for a guild.
/// </summary>
/// <param name="guildId">The target guild.</param>
public Task<IReadOnlyList<DiscordGuildApplicationCommandPermission>> GetGuildApplicationCommandPermissionsAsync(ulong guildId) =>
this.ApiClient.GetGuildApplicationCommandPermissionsAsync(this.CurrentApplication.Id, guildId);
/// <summary>
/// Gets the permissions for a guild command.
/// </summary>
/// <param name="guildId">The target guild.</param>
/// <param name="commandId">The target command id.</param>
public Task<DiscordGuildApplicationCommandPermission> GetApplicationCommandPermissionAsync(ulong guildId, ulong commandId) =>
this.ApiClient.GetGuildApplicationCommandPermissionAsync(this.CurrentApplication.Id, guildId, commandId);
#endregion
#region Internal Caching Methods
/// <summary>
/// Gets the internal cached threads.
/// </summary>
/// <param name="threadId">The target thread id.</param>
/// <returns>The requested thread.</returns>
internal DiscordThreadChannel InternalGetCachedThread(ulong threadId)
{
if (this.Guilds == null)
return null;
foreach (var guild in this.Guilds.Values)
if (guild.Threads.TryGetValue(threadId, out var foundThread))
return foundThread;
return null;
}
/// <summary>
/// Gets the internal cached scheduled event.
/// </summary>
/// <param name="scheduledEventId">The target scheduled event id.</param>
/// <returns>The requested scheduled event.</returns>
internal DiscordScheduledEvent InternalGetCachedScheduledEvent(ulong scheduledEventId)
{
if (this.Guilds == null)
return null;
foreach (var guild in this.Guilds.Values)
if (guild.ScheduledEvents.TryGetValue(scheduledEventId, out var foundScheduledEvent))
return foundScheduledEvent;
return null;
}
/// <summary>
/// Gets the internal cached channel.
/// </summary>
/// <param name="channelId">The target channel id.</param>
/// <returns>The requested channel.</returns>
internal DiscordChannel InternalGetCachedChannel(ulong channelId, ulong? guildId = null)
{
if (this.Guilds == null)
return null;
foreach (var guild in this.Guilds.Values)
if (guild.Channels.TryGetValue(channelId, out var foundChannel))
{
if (guildId.HasValue)
foundChannel.GuildId = guildId;
return foundChannel;
}
return null;
}
/// <summary>
/// Gets the internal cached guild.
/// </summary>
/// <param name="guildId">The target guild id.</param>
/// <returns>The requested guild.</returns>
internal DiscordGuild InternalGetCachedGuild(ulong? guildId)
{
if (this.GuildsInternal != null && guildId.HasValue)
{
if (this.GuildsInternal.TryGetValue(guildId.Value, out var guild))
return guild;
}
return null;
}
/// <summary>
/// Updates a message.
/// </summary>
/// <param name="message">The message to update.</param>
/// <param name="author">The author to update.</param>
/// <param name="guild">The guild to update.</param>
/// <param name="member">The member to update.</param>
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;
}
/// <summary>
/// Updates a scheduled event.
/// </summary>
/// <param name="scheduledEvent">The scheduled event to update.</param>
/// <param name="guild">The guild to update.</param>
/// <returns>The updated scheduled event.</returns>
private DiscordScheduledEvent UpdateScheduledEvent(DiscordScheduledEvent scheduledEvent, DiscordGuild guild)
{
if (scheduledEvent != null)
{
_ = guild.ScheduledEventsInternal.AddOrUpdate(scheduledEvent.Id, scheduledEvent, (id, old) =>
{
old.Discord = this;
old.Description = scheduledEvent.Description;
old.ChannelId = scheduledEvent.ChannelId;
old.EntityId = scheduledEvent.EntityId;
old.EntityType = scheduledEvent.EntityType;
old.EntityMetadata = scheduledEvent.EntityMetadata;
old.PrivacyLevel = scheduledEvent.PrivacyLevel;
old.Name = scheduledEvent.Name;
old.Status = scheduledEvent.Status;
old.UserCount = scheduledEvent.UserCount;
old.ScheduledStartTimeRaw = scheduledEvent.ScheduledStartTimeRaw;
old.ScheduledEndTimeRaw = scheduledEvent.ScheduledEndTimeRaw;
return old;
});
}
return scheduledEvent;
}
/// <summary>
/// Updates a user.
/// </summary>
/// <param name="usr">The user to update.</param>
/// <param name="guildId">The guild id to update.</param>
/// <param name="guild">The guild to update.</param>
/// <param name="mbr">The member to update.</param>
/// <returns>The updated user.</returns>
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;
old.BannerHash = usr.BannerHash;
old.BannerColorInternal = usr.BannerColorInternal;
old.AvatarDecorationHash = usr.AvatarDecorationHash;
old.ThemeColorsInternal = usr.ThemeColorsInternal;
old.Pronouns = usr.Pronouns;
old.Locale = usr.Locale;
return old;
});
usr = new DiscordMember(mbr) { Discord = this, GuildId = guildId.Value };
}
var intents = this.Configuration.Intents;
DiscordMember member = default;
if (!intents.HasAllPrivilegedIntents() || guild.IsLarge) // we have the necessary privileged intents, no need to worry about caching here unless guild is large.
{
if (guild?.MembersInternal.TryGetValue(usr.Id, out member) == false)
{
if (intents.HasIntent(DiscordIntents.GuildMembers) || this.Configuration.AlwaysCacheMembers) // member can be updated by events, so cache it
{
guild.MembersInternal.TryAdd(usr.Id, (DiscordMember)usr);
}
}
else if (intents.HasIntent(DiscordIntents.GuildPresences) || this.Configuration.AlwaysCacheMembers) // we can attempt to update it if it's already in cache.
{
if (!intents.HasIntent(DiscordIntents.GuildMembers)) // no need to update if we already have the member events
{
_ = guild.MembersInternal.TryUpdate(usr.Id, (DiscordMember)usr, member);
}
}
}
}
else if (usr.Username != null) // check if not a skeleton user
{
_ = this.UserCache.AddOrUpdate(usr.Id, usr, (id, old) =>
{
old.Username = usr.Username;
old.Discriminator = usr.Discriminator;
old.AvatarHash = usr.AvatarHash;
old.BannerHash = usr.BannerHash;
old.BannerColorInternal = usr.BannerColorInternal;
old.AvatarDecorationHash = usr.AvatarDecorationHash;
old.ThemeColorsInternal = usr.ThemeColorsInternal;
old.Pronouns = usr.Pronouns;
old.Locale = usr.Locale;
return old;
});
}
return usr;
}
/// <summary>
/// Updates the cached scheduled events in a guild.
/// </summary>
/// <param name="guild">The guild.</param>
/// <param name="rawEvents">The raw events.</param>
private void UpdateCachedScheduledEvent(DiscordGuild guild, JArray rawEvents)
{
if (this._disposed)
return;
if (rawEvents != null)
{
guild.ScheduledEventsInternal.Clear();
foreach (var xj in rawEvents)
{
var xtm = xj.ToDiscordObject<DiscordScheduledEvent>();
xtm.Discord = this;
guild.ScheduledEventsInternal[xtm.Id] = xtm;
}
}
}
/// <summary>
/// Updates the cached guild.
/// </summary>
/// <param name="newGuild">The new guild.</param>
/// <param name="rawMembers">The raw members.</param>
private void UpdateCachedGuild(DiscordGuild newGuild, JArray rawMembers)
{
if (this._disposed)
return;
if (!this.GuildsInternal.ContainsKey(newGuild.Id))
this.GuildsInternal[newGuild.Id] = newGuild;
var guild = this.GuildsInternal[newGuild.Id];
if (newGuild.ChannelsInternal != null && !newGuild.ChannelsInternal.IsEmpty)
{
foreach (var channel in newGuild.ChannelsInternal.Values)
{
if (guild.ChannelsInternal.TryGetValue(channel.Id, out _)) continue;
channel.Initialize(this);
guild.ChannelsInternal[channel.Id] = channel;
}
}
if (newGuild.ThreadsInternal != null && !newGuild.ThreadsInternal.IsEmpty)
{
foreach (var thread in newGuild.ThreadsInternal.Values)
{
if (guild.ThreadsInternal.TryGetValue(thread.Id, out _)) continue;
guild.ThreadsInternal[thread.Id] = thread;
}
}
if (newGuild.ScheduledEventsInternal != null && !newGuild.ScheduledEventsInternal.IsEmpty)
{
foreach (var @event in newGuild.ScheduledEventsInternal.Values)
{
if (guild.ScheduledEventsInternal.TryGetValue(@event.Id, out _)) continue;
guild.ScheduledEventsInternal[@event.Id] = @event;
}
}
foreach (var newEmoji in newGuild.EmojisInternal.Values)
_ = guild.EmojisInternal.GetOrAdd(newEmoji.Id, _ => newEmoji);
foreach (var newSticker in newGuild.StickersInternal.Values)
_ = guild.StickersInternal.GetOrAdd(newSticker.Id, _ => newSticker);
foreach (var newStageInstance in newGuild.StageInstancesInternal.Values)
_ = guild.StageInstancesInternal.GetOrAdd(newStageInstance.Id, _ => newStageInstance);
if (rawMembers != null)
{
guild.MembersInternal.Clear();
foreach (var xj in rawMembers)
{
var xtm = xj.ToDiscordObject<TransportMember>();
var xu = new DiscordUser(xtm.User) { Discord = this };
_ = this.UserCache.AddOrUpdate(xtm.User.Id, xu, (id, old) =>
{
old.Username = xu.Username;
old.Discriminator = xu.Discriminator;
old.AvatarHash = xu.AvatarHash;
old.PremiumType = xu.PremiumType;
return old;
});
guild.MembersInternal[xtm.User.Id] = new DiscordMember(xtm) { Discord = this, GuildId = guild.Id };
}
}
foreach (var role in newGuild.RolesInternal.Values)
{
if (guild.RolesInternal.TryGetValue(role.Id, out _)) continue;
role.GuildId = guild.Id;
guild.RolesInternal[role.Id] = role;
}
guild.Name = newGuild.Name;
guild.AfkChannelId = newGuild.AfkChannelId;
guild.AfkTimeout = newGuild.AfkTimeout;
guild.DefaultMessageNotifications = newGuild.DefaultMessageNotifications;
guild.RawFeatures = newGuild.RawFeatures;
guild.IconHash = newGuild.IconHash;
guild.MfaLevel = newGuild.MfaLevel;
guild.OwnerId = newGuild.OwnerId;
guild.VoiceRegionId = newGuild.VoiceRegionId;
guild.SplashHash = newGuild.SplashHash;
guild.VerificationLevel = newGuild.VerificationLevel;
guild.WidgetEnabled = newGuild.WidgetEnabled;
guild.WidgetChannelId = newGuild.WidgetChannelId;
guild.ExplicitContentFilter = newGuild.ExplicitContentFilter;
guild.PremiumTier = newGuild.PremiumTier;
guild.PremiumSubscriptionCount = newGuild.PremiumSubscriptionCount;
guild.PremiumProgressBarEnabled = newGuild.PremiumProgressBarEnabled;
guild.BannerHash = newGuild.BannerHash;
guild.Description = newGuild.Description;
guild.VanityUrlCode = newGuild.VanityUrlCode;
guild.SystemChannelId = newGuild.SystemChannelId;
guild.SystemChannelFlags = newGuild.SystemChannelFlags;
guild.DiscoverySplashHash = newGuild.DiscoverySplashHash;
guild.MaxMembers = newGuild.MaxMembers;
guild.MaxPresences = newGuild.MaxPresences;
guild.ApproximateMemberCount = newGuild.ApproximateMemberCount;
guild.ApproximatePresenceCount = newGuild.ApproximatePresenceCount;
guild.MaxVideoChannelUsers = newGuild.MaxVideoChannelUsers;
guild.PreferredLocale = newGuild.PreferredLocale;
guild.RulesChannelId = newGuild.RulesChannelId;
guild.PublicUpdatesChannelId = newGuild.PublicUpdatesChannelId;
guild.ApplicationId = newGuild.ApplicationId;
// fields not sent for update:
// - guild.Channels
// - voice states
// - guild.JoinedAt = new_guild.JoinedAt;
// - guild.Large = new_guild.Large;
// - guild.MemberCount = Math.Max(new_guild.MemberCount, guild._members.Count);
// - guild.Unavailable = new_guild.Unavailable;
}
/// <summary>
/// Populates the message reactions and cache.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="author">The author.</param>
/// <param name="member">The member.</param>
private void PopulateMessageReactionsAndCache(DiscordMessage message, TransportUser author, TransportMember member)
{
var guild = message.Channel?.Guild ?? this.InternalGetCachedGuild(message.GuildId);
this.UpdateMessage(message, author, guild, member);
message.ReactionsInternal ??= new List<DiscordReaction>();
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();
}
/// <summary>
/// Whether the client is disposed.
/// </summary>
private bool _disposed;
/// <summary>
/// Disposes the client.
/// </summary>
public override void Dispose()
{
if (this._disposed)
return;
this._disposed = true;
GC.SuppressFinalize(this);
this.DisconnectAsync().ConfigureAwait(false).GetAwaiter().GetResult();
this.ApiClient.Rest.Dispose();
this.CurrentUser = null;
var extensions = this._extensions; // prevent _extensions being modified during dispose
this._extensions = null;
foreach (var extension in extensions)
if (extension is IDisposable disposable)
disposable.Dispose();
try
{
this._cancelTokenSource?.Cancel();
this._cancelTokenSource?.Dispose();
}
catch { }
this.GuildsInternal = null;
this._heartbeatTask = null;
}
#endregion
}
diff --git a/DisCatSharp/Clients/DiscordShardedClient.Events.cs b/DisCatSharp/Clients/DiscordShardedClient.Events.cs
index 528cc182e..1921882aa 100644
--- a/DisCatSharp/Clients/DiscordShardedClient.Events.cs
+++ b/DisCatSharp/Clients/DiscordShardedClient.Events.cs
@@ -1,1733 +1,1733 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Threading.Tasks;
using DisCatSharp.Common.Utilities;
using DisCatSharp.EventArgs;
using Microsoft.Extensions.Logging;
namespace DisCatSharp;
/// <summary>
/// Represents a discord sharded client.
/// </summary>
public sealed partial class DiscordShardedClient
{
#region WebSocket
/// <summary>
/// Fired whenever a WebSocket error occurs within the client.
/// </summary>
public event AsyncEventHandler<DiscordClient, SocketErrorEventArgs> SocketErrored
{
add => this._socketErrored.Register(value);
remove => this._socketErrored.Unregister(value);
}
private AsyncEvent<DiscordClient, SocketErrorEventArgs> _socketErrored;
/// <summary>
/// Fired whenever WebSocket connection is established.
/// </summary>
public event AsyncEventHandler<DiscordClient, SocketEventArgs> SocketOpened
{
add => this._socketOpened.Register(value);
remove => this._socketOpened.Unregister(value);
}
private AsyncEvent<DiscordClient, SocketEventArgs> _socketOpened;
/// <summary>
/// Fired whenever WebSocket connection is terminated.
/// </summary>
public event AsyncEventHandler<DiscordClient, SocketCloseEventArgs> SocketClosed
{
add => this._socketClosed.Register(value);
remove => this._socketClosed.Unregister(value);
}
private AsyncEvent<DiscordClient, SocketCloseEventArgs> _socketClosed;
/// <summary>
/// Fired when the client enters ready state.
/// </summary>
public event AsyncEventHandler<DiscordClient, ReadyEventArgs> Ready
{
add => this._ready.Register(value);
remove => this._ready.Unregister(value);
}
private AsyncEvent<DiscordClient, ReadyEventArgs> _ready;
/// <summary>
/// Fired whenever a session is resumed.
/// </summary>
public event AsyncEventHandler<DiscordClient, ReadyEventArgs> Resumed
{
add => this._resumed.Register(value);
remove => this._resumed.Unregister(value);
}
private AsyncEvent<DiscordClient, ReadyEventArgs> _resumed;
/// <summary>
/// Fired on received heartbeat ACK.
/// </summary>
public event AsyncEventHandler<DiscordClient, HeartbeatEventArgs> Heartbeated
{
add => this._heartbeated.Register(value);
remove => this._heartbeated.Unregister(value);
}
private AsyncEvent<DiscordClient, HeartbeatEventArgs> _heartbeated;
#endregion
#region Channel
/// <summary>
/// Fired when a new channel is created.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, ChannelCreateEventArgs> ChannelCreated
{
add => this._channelCreated.Register(value);
remove => this._channelCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, ChannelCreateEventArgs> _channelCreated;
/// <summary>
/// Fired when a channel is updated.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, ChannelUpdateEventArgs> ChannelUpdated
{
add => this._channelUpdated.Register(value);
remove => this._channelUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, ChannelUpdateEventArgs> _channelUpdated;
/// <summary>
/// Fired when a channel is deleted
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, ChannelDeleteEventArgs> ChannelDeleted
{
add => this._channelDeleted.Register(value);
remove => this._channelDeleted.Unregister(value);
}
private AsyncEvent<DiscordClient, ChannelDeleteEventArgs> _channelDeleted;
/// <summary>
/// Fired when a dm channel is deleted
/// For this Event you need the <see cref="DiscordIntents.DirectMessages"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, DmChannelDeleteEventArgs> DmChannelDeleted
{
add => this._dmChannelDeleted.Register(value);
remove => this._dmChannelDeleted.Unregister(value);
}
private AsyncEvent<DiscordClient, DmChannelDeleteEventArgs> _dmChannelDeleted;
/// <summary>
/// Fired whenever a channel's pinned message list is updated.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, ChannelPinsUpdateEventArgs> ChannelPinsUpdated
{
add => this._channelPinsUpdated.Register(value);
remove => this._channelPinsUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, ChannelPinsUpdateEventArgs> _channelPinsUpdated;
#endregion
#region Guild
/// <summary>
/// Fired when the user joins a new guild.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
/// <remarks>[alias="GuildJoined"][alias="JoinedGuild"]</remarks>
public event AsyncEventHandler<DiscordClient, GuildCreateEventArgs> GuildCreated
{
add => this._guildCreated.Register(value);
remove => this._guildCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildCreateEventArgs> _guildCreated;
/// <summary>
/// Fired when a guild is becoming available.
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildCreateEventArgs> GuildAvailable
{
add => this._guildAvailable.Register(value);
remove => this._guildAvailable.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildCreateEventArgs> _guildAvailable;
/// <summary>
/// Fired when a guild is updated.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildUpdateEventArgs> GuildUpdated
{
add => this._guildUpdated.Register(value);
remove => this._guildUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildUpdateEventArgs> _guildUpdated;
/// <summary>
/// Fired when the user leaves or is removed from a guild.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildDeleteEventArgs> GuildDeleted
{
add => this._guildDeleted.Register(value);
remove => this._guildDeleted.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildDeleteEventArgs> _guildDeleted;
/// <summary>
/// Fired when a guild becomes unavailable.
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildDeleteEventArgs> GuildUnavailable
{
add => this._guildUnavailable.Register(value);
remove => this._guildUnavailable.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildDeleteEventArgs> _guildUnavailable;
/// <summary>
/// Fired when all guilds finish streaming from Discord.
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildDownloadCompletedEventArgs> GuildDownloadCompleted
{
add => this._guildDownloadCompleted.Register(value);
remove => this._guildDownloadCompleted.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildDownloadCompletedEventArgs> _guildDownloadCompleted;
/// <summary>
/// Fired when a guilds emojis get updated
/// For this Event you need the <see cref="DiscordIntents.GuildEmojisAndStickers"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildEmojisUpdateEventArgs> GuildEmojisUpdated
{
add => this._guildEmojisUpdated.Register(value);
remove => this._guildEmojisUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildEmojisUpdateEventArgs> _guildEmojisUpdated;
/// <summary>
/// Fired when a guilds stickers get updated
/// For this Event you need the <see cref="DiscordIntents.GuildEmojisAndStickers"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildStickersUpdateEventArgs> GuildStickersUpdated
{
add => this._guildStickersUpdated.Register(value);
remove => this._guildStickersUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildStickersUpdateEventArgs> _guildStickersUpdated;
/// <summary>
/// Fired when a guild integration is updated.
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildIntegrationsUpdateEventArgs> GuildIntegrationsUpdated
{
add => this._guildIntegrationsUpdated.Register(value);
remove => this._guildIntegrationsUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildIntegrationsUpdateEventArgs> _guildIntegrationsUpdated;
#endregion
#region Automod
/// <summary>
/// Fired when an auto mod rule gets created.
/// </summary>
public event AsyncEventHandler<DiscordClient, AutomodRuleCreateEventArgs> AutomodRuleCreated
{
add => this._automodRuleCreated.Register(value);
remove => this._automodRuleCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, AutomodRuleCreateEventArgs> _automodRuleCreated;
/// <summary>
/// Fired when an auto mod rule gets updated.
/// </summary>
public event AsyncEventHandler<DiscordClient, AutomodRuleUpdateEventArgs> AutomodRuleUpdated
{
add => this._automodRuleUpdated.Register(value);
remove => this._automodRuleUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, AutomodRuleUpdateEventArgs> _automodRuleUpdated;
/// <summary>
/// Fired when an auto mod rule gets deleted.
/// </summary>
public event AsyncEventHandler<DiscordClient, AutomodRuleDeleteEventArgs> AutomodRuleDeleted
{
add => this._automodRuleDeleted.Register(value);
remove => this._automodRuleDeleted.Unregister(value);
}
private AsyncEvent<DiscordClient, AutomodRuleDeleteEventArgs> _automodRuleDeleted;
/// <summary>
/// Fired when a rule is triggered and an action is executed.
/// </summary>
public event AsyncEventHandler<DiscordClient, AutomodActionExecutedEventArgs> AutomodActionExecuted
{
add => this._automodActionExecuted.Register(value);
remove => this._automodActionExecuted.Unregister(value);
}
private AsyncEvent<DiscordClient, AutomodActionExecutedEventArgs> _automodActionExecuted;
/// <summary>
/// Fired when a guild audit log entry was created.
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildAuditLogEntryCreateEventArgs> GuildAuditLogEntryCreated
{
add => this._guildAuditLogEntryCreated.Register(value);
remove => this._guildAuditLogEntryCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildAuditLogEntryCreateEventArgs> _guildAuditLogEntryCreated;
#endregion
#region Guild Ban
/// <summary>
/// Fired when a guild ban gets added
/// For this Event you need the <see cref="DiscordIntents.GuildBans"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildBanAddEventArgs> GuildBanAdded
{
add => this._guildBanAdded.Register(value);
remove => this._guildBanAdded.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildBanAddEventArgs> _guildBanAdded;
/// <summary>
/// Fired when a guild ban gets removed
/// For this Event you need the <see cref="DiscordIntents.GuildBans"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildBanRemoveEventArgs> GuildBanRemoved
{
add => this._guildBanRemoved.Register(value);
remove => this._guildBanRemoved.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildBanRemoveEventArgs> _guildBanRemoved;
#endregion
#region Guild Timeout
/// <summary>
/// Fired when a guild member timeout gets added.
/// For this Event you need the <see cref="DiscordIntents.GuildBans"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildMemberTimeoutAddEventArgs> GuildMemberTimeoutAdded
{
add => this._guildMemberTimeoutAdded.Register(value);
remove => this._guildMemberTimeoutAdded.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildMemberTimeoutAddEventArgs> _guildMemberTimeoutAdded;
/// <summary>
/// Fired when a guild member timeout gets changed.
/// For this Event you need the <see cref="DiscordIntents.GuildMembers"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildMemberTimeoutUpdateEventArgs> GuildMemberTimeoutChanged
{
add => this._guildMemberTimeoutChanged.Register(value);
remove => this._guildMemberTimeoutChanged.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildMemberTimeoutUpdateEventArgs> _guildMemberTimeoutChanged;
/// <summary>
/// Fired when a guild member timeout gets removed.
/// For this Event you need the <see cref="DiscordIntents.GuildMembers"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildMemberTimeoutRemoveEventArgs> GuildMemberTimeoutRemoved
{
add => this._guildMemberTimeoutRemoved.Register(value);
remove => this._guildMemberTimeoutRemoved.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildMemberTimeoutRemoveEventArgs> _guildMemberTimeoutRemoved;
#endregion
#region Guild Event
/// <summary>
/// Fired when a scheduled event is created.
/// For this Event you need the <see cref="DiscordIntents.GuildScheduledEvents"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildScheduledEventCreateEventArgs> GuildScheduledEventCreated
{
add => this._guildScheduledEventCreated.Register(value);
remove => this._guildScheduledEventCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildScheduledEventCreateEventArgs> _guildScheduledEventCreated;
/// <summary>
/// Fired when a scheduled event is updated.
/// For this Event you need the <see cref="DiscordIntents.GuildScheduledEvents"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildScheduledEventUpdateEventArgs> GuildScheduledEventUpdated
{
add => this._guildScheduledEventUpdated.Register(value);
remove => this._guildScheduledEventUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildScheduledEventUpdateEventArgs> _guildScheduledEventUpdated;
/// <summary>
/// Fired when a scheduled event is deleted.
/// For this Event you need the <see cref="DiscordIntents.GuildScheduledEvents"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildScheduledEventDeleteEventArgs> GuildScheduledEventDeleted
{
add => this._guildScheduledEventDeleted.Register(value);
remove => this._guildScheduledEventDeleted.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildScheduledEventDeleteEventArgs> _guildScheduledEventDeleted;
/// <summary>
/// Fired when a user subscribes to a scheduled event.
/// For this Event you need the <see cref="DiscordIntents.GuildScheduledEvents"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildScheduledEventUserAddEventArgs> GuildScheduledEventUserAdded
{
add => this._guildScheduledEventUserAdded.Register(value);
remove => this._guildScheduledEventUserAdded.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildScheduledEventUserAddEventArgs> _guildScheduledEventUserAdded;
/// <summary>
/// Fired when a user unsubscribes from a scheduled event.
/// For this Event you need the <see cref="DiscordIntents.GuildScheduledEvents"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildScheduledEventUserRemoveEventArgs> GuildScheduledEventUserRemoved
{
add => this._guildScheduledEventUserRemoved.Register(value);
remove => this._guildScheduledEventUserRemoved.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildScheduledEventUserRemoveEventArgs> _guildScheduledEventUserRemoved;
#endregion
#region Guild Integration
/// <summary>
/// Fired when a guild integration is created.
/// For this Event you need the <see cref="DiscordIntents.GuildIntegrations"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildIntegrationCreateEventArgs> GuildIntegrationCreated
{
add => this._guildIntegrationCreated.Register(value);
remove => this._guildIntegrationCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildIntegrationCreateEventArgs> _guildIntegrationCreated;
/// <summary>
/// Fired when a guild integration is updated.
/// For this Event you need the <see cref="DiscordIntents.GuildIntegrations"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildIntegrationUpdateEventArgs> GuildIntegrationUpdated
{
add => this._guildIntegrationUpdated.Register(value);
remove => this._guildIntegrationUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildIntegrationUpdateEventArgs> _guildIntegrationUpdated;
/// <summary>
/// Fired when a guild integration is deleted.
/// For this Event you need the <see cref="DiscordIntents.GuildIntegrations"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildIntegrationDeleteEventArgs> GuildIntegrationDeleted
{
add => this._guildIntegrationDeleted.Register(value);
remove => this._guildIntegrationDeleted.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildIntegrationDeleteEventArgs> _guildIntegrationDeleted;
#endregion
#region Guild Member
/// <summary>
/// Fired when a new user joins a guild.
/// For this Event you need the <see cref="DiscordIntents.GuildMembers"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildMemberAddEventArgs> GuildMemberAdded
{
add => this._guildMemberAdded.Register(value);
remove => this._guildMemberAdded.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildMemberAddEventArgs> _guildMemberAdded;
/// <summary>
/// Fired when a user is removed from a guild (leave/kick/ban).
/// For this Event you need the <see cref="DiscordIntents.GuildMembers"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildMemberRemoveEventArgs> GuildMemberRemoved
{
add => this._guildMemberRemoved.Register(value);
remove => this._guildMemberRemoved.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildMemberRemoveEventArgs> _guildMemberRemoved;
/// <summary>
/// Fired when a guild member is updated.
/// For this Event you need the <see cref="DiscordIntents.GuildMembers"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildMemberUpdateEventArgs> GuildMemberUpdated
{
add => this._guildMemberUpdated.Register(value);
remove => this._guildMemberUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildMemberUpdateEventArgs> _guildMemberUpdated;
/// <summary>
/// Fired in response to Gateway Request Guild Members.
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildMembersChunkEventArgs> GuildMembersChunked
{
add => this._guildMembersChunk.Register(value);
remove => this._guildMembersChunk.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildMembersChunkEventArgs> _guildMembersChunk;
#endregion
#region Guild Role
/// <summary>
/// Fired when a guild role is created.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildRoleCreateEventArgs> GuildRoleCreated
{
add => this._guildRoleCreated.Register(value);
remove => this._guildRoleCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildRoleCreateEventArgs> _guildRoleCreated;
/// <summary>
/// Fired when a guild role is updated.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildRoleUpdateEventArgs> GuildRoleUpdated
{
add => this._guildRoleUpdated.Register(value);
remove => this._guildRoleUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildRoleUpdateEventArgs> _guildRoleUpdated;
/// <summary>
/// Fired when a guild role is updated.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildRoleDeleteEventArgs> GuildRoleDeleted
{
add => this._guildRoleDeleted.Register(value);
remove => this._guildRoleDeleted.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildRoleDeleteEventArgs> _guildRoleDeleted;
#endregion
#region Invite
/// <summary>
/// Fired when an invite is created.
/// For this Event you need the <see cref="DiscordIntents.GuildInvites"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, InviteCreateEventArgs> InviteCreated
{
add => this._inviteCreated.Register(value);
remove => this._inviteCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, InviteCreateEventArgs> _inviteCreated;
/// <summary>
/// Fired when an invite is deleted.
/// For this Event you need the <see cref="DiscordIntents.GuildInvites"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, InviteDeleteEventArgs> InviteDeleted
{
add => this._inviteDeleted.Register(value);
remove => this._inviteDeleted.Unregister(value);
}
private AsyncEvent<DiscordClient, InviteDeleteEventArgs> _inviteDeleted;
#endregion
#region Message
/// <summary>
/// Fired when a message is created.
/// For this Event you need the <see cref="DiscordIntents.GuildMessages"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, MessageCreateEventArgs> MessageCreated
{
add => this._messageCreated.Register(value);
remove => this._messageCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, MessageCreateEventArgs> _messageCreated;
/// <summary>
/// Fired when a message is updated.
/// For this Event you need the <see cref="DiscordIntents.GuildMessages"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, MessageUpdateEventArgs> MessageUpdated
{
add => this._messageUpdated.Register(value);
remove => this._messageUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, MessageUpdateEventArgs> _messageUpdated;
/// <summary>
/// Fired when a message is deleted.
/// For this Event you need the <see cref="DiscordIntents.GuildMessages"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, MessageDeleteEventArgs> MessageDeleted
{
add => this._messageDeleted.Register(value);
remove => this._messageDeleted.Unregister(value);
}
private AsyncEvent<DiscordClient, MessageDeleteEventArgs> _messageDeleted;
/// <summary>
/// Fired when multiple messages are deleted at once.
/// For this Event you need the <see cref="DiscordIntents.GuildMessages"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, MessageBulkDeleteEventArgs> MessagesBulkDeleted
{
add => this._messageBulkDeleted.Register(value);
remove => this._messageBulkDeleted.Unregister(value);
}
private AsyncEvent<DiscordClient, MessageBulkDeleteEventArgs> _messageBulkDeleted;
#endregion
#region Message Reaction
/// <summary>
/// Fired when a reaction gets added to a message.
/// For this Event you need the <see cref="DiscordIntents.GuildMessageReactions"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, MessageReactionAddEventArgs> MessageReactionAdded
{
add => this._messageReactionAdded.Register(value);
remove => this._messageReactionAdded.Unregister(value);
}
private AsyncEvent<DiscordClient, MessageReactionAddEventArgs> _messageReactionAdded;
/// <summary>
/// Fired when a reaction gets removed from a message.
/// For this Event you need the <see cref="DiscordIntents.GuildMessageReactions"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, MessageReactionRemoveEventArgs> MessageReactionRemoved
{
add => this._messageReactionRemoved.Register(value);
remove => this._messageReactionRemoved.Unregister(value);
}
private AsyncEvent<DiscordClient, MessageReactionRemoveEventArgs> _messageReactionRemoved;
/// <summary>
/// Fired when all reactions get removed from a message.
/// For this Event you need the <see cref="DiscordIntents.GuildMessageReactions"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, MessageReactionsClearEventArgs> MessageReactionsCleared
{
add => this._messageReactionsCleared.Register(value);
remove => this._messageReactionsCleared.Unregister(value);
}
private AsyncEvent<DiscordClient, MessageReactionsClearEventArgs> _messageReactionsCleared;
/// <summary>
/// Fired when all reactions of a specific reaction are removed from a message.
/// For this Event you need the <see cref="DiscordIntents.GuildMessageReactions"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, MessageReactionRemoveEmojiEventArgs> MessageReactionRemovedEmoji
{
add => this._messageReactionRemovedEmoji.Register(value);
remove => this._messageReactionRemovedEmoji.Unregister(value);
}
private AsyncEvent<DiscordClient, MessageReactionRemoveEmojiEventArgs> _messageReactionRemovedEmoji;
#endregion
#region Stage Instance
/// <summary>
/// Fired when a Stage Instance is created.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, StageInstanceCreateEventArgs> StageInstanceCreated
{
add => this._stageInstanceCreated.Register(value);
remove => this._stageInstanceCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, StageInstanceCreateEventArgs> _stageInstanceCreated;
/// <summary>
/// Fired when a Stage Instance is updated.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, StageInstanceUpdateEventArgs> StageInstanceUpdated
{
add => this._stageInstanceUpdated.Register(value);
remove => this._stageInstanceUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, StageInstanceUpdateEventArgs> _stageInstanceUpdated;
/// <summary>
/// Fired when a Stage Instance is deleted.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, StageInstanceDeleteEventArgs> StageInstanceDeleted
{
add => this._stageInstanceDeleted.Register(value);
remove => this._stageInstanceDeleted.Unregister(value);
}
private AsyncEvent<DiscordClient, StageInstanceDeleteEventArgs> _stageInstanceDeleted;
#endregion
#region Thread
/// <summary>
/// Fired when a thread is created.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, ThreadCreateEventArgs> ThreadCreated
{
add => this._threadCreated.Register(value);
remove => this._threadCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, ThreadCreateEventArgs> _threadCreated;
/// <summary>
/// Fired when a thread is updated.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, ThreadUpdateEventArgs> ThreadUpdated
{
add => this._threadUpdated.Register(value);
remove => this._threadUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, ThreadUpdateEventArgs> _threadUpdated;
/// <summary>
/// Fired when a thread is deleted.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, ThreadDeleteEventArgs> ThreadDeleted
{
add => this._threadDeleted.Register(value);
remove => this._threadDeleted.Unregister(value);
}
private AsyncEvent<DiscordClient, ThreadDeleteEventArgs> _threadDeleted;
/// <summary>
/// Fired when a thread member is updated.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, ThreadListSyncEventArgs> ThreadListSynced
{
add => this._threadListSynced.Register(value);
remove => this._threadListSynced.Unregister(value);
}
private AsyncEvent<DiscordClient, ThreadListSyncEventArgs> _threadListSynced;
/// <summary>
/// Fired when a thread member is updated.
/// For this Event you need the <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, ThreadMemberUpdateEventArgs> ThreadMemberUpdated
{
add => this._threadMemberUpdated.Register(value);
remove => this._threadMemberUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, ThreadMemberUpdateEventArgs> _threadMemberUpdated;
/// <summary>
/// Fired when the thread members are updated.
/// For this Event you need the <see cref="DiscordIntents.GuildMembers"/> or <see cref="DiscordIntents.Guilds"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, ThreadMembersUpdateEventArgs> ThreadMembersUpdated
{
add => this._threadMembersUpdated.Register(value);
remove => this._threadMembersUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, ThreadMembersUpdateEventArgs> _threadMembersUpdated;
#endregion
#region Activities
/// <summary>
/// Fired when a embedded activity has been updated.
/// </summary>
public event AsyncEventHandler<DiscordClient, EmbeddedActivityUpdateEventArgs> EmbeddedActivityUpdated
{
add => this._embeddedActivityUpdated.Register(value);
remove => this._embeddedActivityUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, EmbeddedActivityUpdateEventArgs> _embeddedActivityUpdated;
#endregion
#region User/Presence Update
/// <summary>
/// Fired when a presence has been updated.
/// For this Event you need the <see cref="DiscordIntents.GuildPresences"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, PresenceUpdateEventArgs> PresenceUpdated
{
add => this._presenceUpdated.Register(value);
remove => this._presenceUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, PresenceUpdateEventArgs> _presenceUpdated;
/// <summary>
/// Fired when the current user updates their settings.
/// For this Event you need the <see cref="DiscordIntents.GuildPresences"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, UserSettingsUpdateEventArgs> UserSettingsUpdated
{
add => this._userSettingsUpdated.Register(value);
remove => this._userSettingsUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, UserSettingsUpdateEventArgs> _userSettingsUpdated;
/// <summary>
/// Fired when properties about the current user change.
/// For this Event you need the <see cref="DiscordIntents.GuildPresences"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
/// <remarks>
/// NB: This event only applies for changes to the <b>current user</b>, the client that is connected to Discord.
/// </remarks>
public event AsyncEventHandler<DiscordClient, UserUpdateEventArgs> UserUpdated
{
add => this._userUpdated.Register(value);
remove => this._userUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, UserUpdateEventArgs> _userUpdated;
#endregion
#region Voice
/// <summary>
/// Fired when someone joins/leaves/moves voice channels.
/// For this Event you need the <see cref="DiscordIntents.GuildVoiceStates"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, VoiceStateUpdateEventArgs> VoiceStateUpdated
{
add => this._voiceStateUpdated.Register(value);
remove => this._voiceStateUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, VoiceStateUpdateEventArgs> _voiceStateUpdated;
/// <summary>
/// Fired when a guild's voice server is updated.
/// For this Event you need the <see cref="DiscordIntents.GuildVoiceStates"/> intent specified in <seealso cref="DiscordConfiguration.Intents"/>
/// </summary>
public event AsyncEventHandler<DiscordClient, VoiceServerUpdateEventArgs> VoiceServerUpdated
{
add => this._voiceServerUpdated.Register(value);
remove => this._voiceServerUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, VoiceServerUpdateEventArgs> _voiceServerUpdated;
#endregion
#region Application
/// <summary>
/// Fired when a new application command is registered.
/// </summary>
public event AsyncEventHandler<DiscordClient, ApplicationCommandEventArgs> ApplicationCommandCreated
{
add => this._applicationCommandCreated.Register(value);
remove => this._applicationCommandCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, ApplicationCommandEventArgs> _applicationCommandCreated;
/// <summary>
/// Fired when an application command is updated.
/// </summary>
public event AsyncEventHandler<DiscordClient, ApplicationCommandEventArgs> ApplicationCommandUpdated
{
add => this._applicationCommandUpdated.Register(value);
remove => this._applicationCommandUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, ApplicationCommandEventArgs> _applicationCommandUpdated;
/// <summary>
/// Fired when an application command is deleted.
/// </summary>
public event AsyncEventHandler<DiscordClient, ApplicationCommandEventArgs> ApplicationCommandDeleted
{
add => this._applicationCommandDeleted.Register(value);
remove => this._applicationCommandDeleted.Unregister(value);
}
private AsyncEvent<DiscordClient, ApplicationCommandEventArgs> _applicationCommandDeleted;
/// <summary>
/// Fired when a new application command is registered.
/// </summary>
public event AsyncEventHandler<DiscordClient, GuildApplicationCommandCountEventArgs> GuildApplicationCommandCountUpdated
{
add => this._guildApplicationCommandCountUpdated.Register(value);
remove => this._guildApplicationCommandCountUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, GuildApplicationCommandCountEventArgs> _guildApplicationCommandCountUpdated;
/// <summary>
/// Fired when a user uses a context menu.
/// </summary>
public event AsyncEventHandler<DiscordClient, ContextMenuInteractionCreateEventArgs> ContextMenuInteractionCreated
{
add => this._contextMenuInteractionCreated.Register(value);
remove => this._contextMenuInteractionCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, ContextMenuInteractionCreateEventArgs> _contextMenuInteractionCreated;
/// <summary>
/// Fired when application command permissions gets updated.
/// </summary>
public event AsyncEventHandler<DiscordClient, ApplicationCommandPermissionsUpdateEventArgs> ApplicationCommandPermissionsUpdated
{
add => this._applicationCommandPermissionsUpdated.Register(value);
remove => this._applicationCommandPermissionsUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, ApplicationCommandPermissionsUpdateEventArgs> _applicationCommandPermissionsUpdated;
#endregion
#region Misc
/// <summary>
/// Fired when an interaction is invoked.
/// </summary>
public event AsyncEventHandler<DiscordClient, InteractionCreateEventArgs> InteractionCreated
{
add => this._interactionCreated.Register(value);
remove => this._interactionCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, InteractionCreateEventArgs> _interactionCreated;
/// <summary>
/// Fired when a component is invoked.
/// </summary>
public event AsyncEventHandler<DiscordClient, ComponentInteractionCreateEventArgs> ComponentInteractionCreated
{
add => this._componentInteractionCreated.Register(value);
remove => this._componentInteractionCreated.Unregister(value);
}
private AsyncEvent<DiscordClient, ComponentInteractionCreateEventArgs> _componentInteractionCreated;
/// <summary>
/// Fired when a user starts typing in a channel.
/// </summary>
public event AsyncEventHandler<DiscordClient, TypingStartEventArgs> TypingStarted
{
add => this._typingStarted.Register(value);
remove => this._typingStarted.Unregister(value);
}
private AsyncEvent<DiscordClient, TypingStartEventArgs> _typingStarted;
/// <summary>
/// Fired when an unknown event gets received.
/// </summary>
public event AsyncEventHandler<DiscordClient, UnknownEventArgs> UnknownEvent
{
add => this._unknownEvent.Register(value);
remove => this._unknownEvent.Unregister(value);
}
private AsyncEvent<DiscordClient, UnknownEventArgs> _unknownEvent;
/// <summary>
/// Fired whenever webhooks update.
/// </summary>
public event AsyncEventHandler<DiscordClient, WebhooksUpdateEventArgs> WebhooksUpdated
{
add => this._webhooksUpdated.Register(value);
remove => this._webhooksUpdated.Unregister(value);
}
private AsyncEvent<DiscordClient, WebhooksUpdateEventArgs> _webhooksUpdated;
/// <summary>
/// Fired whenever an error occurs within an event handler.
/// </summary>
public event AsyncEventHandler<DiscordClient, ClientErrorEventArgs> ClientErrored
{
add => this._clientErrored.Register(value);
remove => this._clientErrored.Unregister(value);
}
private AsyncEvent<DiscordClient, ClientErrorEventArgs> _clientErrored;
#endregion
#region Error Handling
/// <summary>
/// Handles event errors.
/// </summary>
/// <param name="asyncEvent">The event.</param>
/// <param name="ex">The exception.</param>
/// <param name="handler">The event handler.</param>
/// <param name="sender">The sender.</param>
/// <param name="eventArgs">The event args.</param>
internal void EventErrorHandler<TArgs>(AsyncEvent<DiscordClient, TArgs> asyncEvent, Exception ex, AsyncEventHandler<DiscordClient, TArgs> handler, DiscordClient sender, TArgs eventArgs)
where TArgs : AsyncEventArgs
{
if (ex is AsyncEventTimeoutException)
{
this.Logger.LogWarning(LoggerEvents.EventHandlerException, $"An event handler for {asyncEvent.Name} took too long to execute. Defined as \"{handler.Method.ToString().Replace(handler.Method.ReturnType.ToString(), "").TrimStart()}\" located in \"{handler.Method.DeclaringType}\".");
return;
}
this.Logger.LogError(LoggerEvents.EventHandlerException, ex, "Event handler exception for event {0} thrown from {1} (defined in {2})", asyncEvent.Name, handler.Method, handler.Method.DeclaringType);
this._clientErrored.InvokeAsync(sender, new ClientErrorEventArgs(this.ShardClients[0].ServiceProvider) { EventName = asyncEvent.Name, Exception = ex }).ConfigureAwait(false).GetAwaiter().GetResult();
}
/// <summary>
/// Fired on heartbeat attempt cancellation due to too many failed heartbeats.
/// </summary>
public event AsyncEventHandler<DiscordClient, ZombiedEventArgs> Zombied
{
add => this._zombied.Register(value);
remove => this._zombied.Unregister(value);
}
private AsyncEvent<DiscordClient, ZombiedEventArgs> _zombied;
/// <summary>
/// Fired when a gateway payload is received.
/// </summary>
public event AsyncEventHandler<DiscordClient, PayloadReceivedEventArgs> PayloadReceived
{
add => this._payloadReceived.Register(value);
remove => this._payloadReceived.Unregister(value);
}
private AsyncEvent<DiscordClient, PayloadReceivedEventArgs> _payloadReceived;
/// <summary>
/// Fired when a event handler throws an exception.
/// </summary>
/// <param name="asyncEvent">The event.</param>
/// <param name="ex">The exception.</param>
/// <param name="handler">The event handler.</param>
/// <param name="sender">The sender.</param>
/// <param name="eventArgs">The event args.</param>
private void Goof<TArgs>(AsyncEvent<DiscordClient, TArgs> asyncEvent, Exception ex, AsyncEventHandler<DiscordClient, TArgs> handler, DiscordClient sender, TArgs eventArgs)
where TArgs : AsyncEventArgs => this.Logger.LogCritical(LoggerEvents.EventHandlerException, ex, "Exception event handler {0} (defined in {1}) threw an exception", handler.Method, handler.Method.DeclaringType);
#endregion
#region Event Dispatchers
/// <summary>
/// Handles the client zombied event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_Zombied(DiscordClient client, ZombiedEventArgs e)
=> this._zombied.InvokeAsync(client, e);
/// <summary>
/// Handles the guild member timeout removed event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildMemberTimeoutRemoved(DiscordClient client, GuildMemberTimeoutRemoveEventArgs e)
=> this._guildMemberTimeoutRemoved.InvokeAsync(client, e);
/// <summary>
/// Handles the guild member timeout changed event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildMemberTimeoutChanged(DiscordClient client, GuildMemberTimeoutUpdateEventArgs e)
=> this._guildMemberTimeoutChanged.InvokeAsync(client, e);
/// <summary>
/// Handles the guild member timeout added event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildMemberTimeoutAdded(DiscordClient client, GuildMemberTimeoutAddEventArgs e)
=> this._guildMemberTimeoutAdded.InvokeAsync(client, e);
/// <summary>
/// Handles the embedded activity updated event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_EmbeddedActivityUpdated(DiscordClient client, EmbeddedActivityUpdateEventArgs e)
=> this._embeddedActivityUpdated.InvokeAsync(client, e);
/// <summary>
/// Handles the payload received event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_PayloadReceived(DiscordClient client, PayloadReceivedEventArgs e)
=> this._payloadReceived.InvokeAsync(client, e);
/// <summary>
/// Handles the client error event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_ClientError(DiscordClient client, ClientErrorEventArgs e)
=> this._clientErrored.InvokeAsync(client, e);
/// <summary>
/// Handles the socket error event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_SocketError(DiscordClient client, SocketErrorEventArgs e)
=> this._socketErrored.InvokeAsync(client, e);
/// <summary>
/// Handles the socket opened event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_SocketOpened(DiscordClient client, SocketEventArgs e)
=> this._socketOpened.InvokeAsync(client, e);
/// <summary>
/// Handles the socket closed event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_SocketClosed(DiscordClient client, SocketCloseEventArgs e)
=> this._socketClosed.InvokeAsync(client, e);
/// <summary>
/// Handles the ready event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_Ready(DiscordClient client, ReadyEventArgs e)
=> this._ready.InvokeAsync(client, e);
/// <summary>
/// Handles the resumed event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_Resumed(DiscordClient client, ReadyEventArgs e)
=> this._resumed.InvokeAsync(client, e);
/// <summary>
/// Handles the channel created event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_ChannelCreated(DiscordClient client, ChannelCreateEventArgs e)
=> this._channelCreated.InvokeAsync(client, e);
/// <summary>
/// Handles the channel updated event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_ChannelUpdated(DiscordClient client, ChannelUpdateEventArgs e)
=> this._channelUpdated.InvokeAsync(client, e);
/// <summary>
/// Handles the channel deleted.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_ChannelDeleted(DiscordClient client, ChannelDeleteEventArgs e)
=> this._channelDeleted.InvokeAsync(client, e);
/// <summary>
/// Handles the dm channel deleted event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_DMChannelDeleted(DiscordClient client, DmChannelDeleteEventArgs e)
=> this._dmChannelDeleted.InvokeAsync(client, e);
/// <summary>
/// Handles the channel pins updated event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_ChannelPinsUpdated(DiscordClient client, ChannelPinsUpdateEventArgs e)
=> this._channelPinsUpdated.InvokeAsync(client, e);
/// <summary>
/// Handles the guild created event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildCreated(DiscordClient client, GuildCreateEventArgs e)
=> this._guildCreated.InvokeAsync(client, e);
/// <summary>
/// Handles the guild available event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildAvailable(DiscordClient client, GuildCreateEventArgs e)
=> this._guildAvailable.InvokeAsync(client, e);
/// <summary>
/// Handles the guild updated event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildUpdated(DiscordClient client, GuildUpdateEventArgs e)
=> this._guildUpdated.InvokeAsync(client, e);
/// <summary>
/// Handles the guild deleted event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildDeleted(DiscordClient client, GuildDeleteEventArgs e)
=> this._guildDeleted.InvokeAsync(client, e);
/// <summary>
/// Handles the guild unavailable event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildUnavailable(DiscordClient client, GuildDeleteEventArgs e)
=> this._guildUnavailable.InvokeAsync(client, e);
/// <summary>
/// Handles the guild download completed event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildDownloadCompleted(DiscordClient client, GuildDownloadCompletedEventArgs e)
=> this._guildDownloadCompleted.InvokeAsync(client, e);
/// <summary>
/// Handles the message created event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_MessageCreated(DiscordClient client, MessageCreateEventArgs e)
=> this._messageCreated.InvokeAsync(client, e);
/// <summary>
/// Handles the invite created event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_InviteCreated(DiscordClient client, InviteCreateEventArgs e)
=> this._inviteCreated.InvokeAsync(client, e);
/// <summary>
/// Handles the invite deleted event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_InviteDeleted(DiscordClient client, InviteDeleteEventArgs e)
=> this._inviteDeleted.InvokeAsync(client, e);
/// <summary>
/// Handles the presence update event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_PresenceUpdate(DiscordClient client, PresenceUpdateEventArgs e)
=> this._presenceUpdated.InvokeAsync(client, e);
/// <summary>
/// Handles the guild ban add event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildBanAdd(DiscordClient client, GuildBanAddEventArgs e)
=> this._guildBanAdded.InvokeAsync(client, e);
/// <summary>
/// Handles the guild ban remove event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildBanRemove(DiscordClient client, GuildBanRemoveEventArgs e)
=> this._guildBanRemoved.InvokeAsync(client, e);
/// <summary>
/// Handles the guild emojis update event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildEmojisUpdate(DiscordClient client, GuildEmojisUpdateEventArgs e)
=> this._guildEmojisUpdated.InvokeAsync(client, e);
/// <summary>
/// Handles the guild stickers update event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildStickersUpdate(DiscordClient client, GuildStickersUpdateEventArgs e)
=> this._guildStickersUpdated.InvokeAsync(client, e);
/// <summary>
/// Handles the guild integrations update event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildIntegrationsUpdate(DiscordClient client, GuildIntegrationsUpdateEventArgs e)
=> this._guildIntegrationsUpdated.InvokeAsync(client, e);
/// <summary>
/// Handles the guild member add event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildMemberAdd(DiscordClient client, GuildMemberAddEventArgs e)
=> this._guildMemberAdded.InvokeAsync(client, e);
/// <summary>
/// Handles the guild member remove event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildMemberRemove(DiscordClient client, GuildMemberRemoveEventArgs e)
=> this._guildMemberRemoved.InvokeAsync(client, e);
/// <summary>
/// Handles the guild member update event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildMemberUpdate(DiscordClient client, GuildMemberUpdateEventArgs e)
=> this._guildMemberUpdated.InvokeAsync(client, e);
/// <summary>
/// Handles the guild role create event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildRoleCreate(DiscordClient client, GuildRoleCreateEventArgs e)
=> this._guildRoleCreated.InvokeAsync(client, e);
/// <summary>
/// Handles the guild role update event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildRoleUpdate(DiscordClient client, GuildRoleUpdateEventArgs e)
=> this._guildRoleUpdated.InvokeAsync(client, e);
/// <summary>
/// Handles the guild role delete event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildRoleDelete(DiscordClient client, GuildRoleDeleteEventArgs e)
=> this._guildRoleDeleted.InvokeAsync(client, e);
/// <summary>
/// Handles the message update event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_MessageUpdate(DiscordClient client, MessageUpdateEventArgs e)
=> this._messageUpdated.InvokeAsync(client, e);
/// <summary>
/// Handles the message delete event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_MessageDelete(DiscordClient client, MessageDeleteEventArgs e)
=> this._messageDeleted.InvokeAsync(client, e);
/// <summary>
/// Handles the message bulk delete event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_MessageBulkDelete(DiscordClient client, MessageBulkDeleteEventArgs e)
=> this._messageBulkDeleted.InvokeAsync(client, e);
/// <summary>
/// Handles the typing start event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_TypingStart(DiscordClient client, TypingStartEventArgs e)
=> this._typingStarted.InvokeAsync(client, e);
/// <summary>
/// Handles the user settings update event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_UserSettingsUpdate(DiscordClient client, UserSettingsUpdateEventArgs e)
=> this._userSettingsUpdated.InvokeAsync(client, e);
/// <summary>
/// Handles the user update event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_UserUpdate(DiscordClient client, UserUpdateEventArgs e)
=> this._userUpdated.InvokeAsync(client, e);
/// <summary>
/// Handles the voice state update event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_VoiceStateUpdate(DiscordClient client, VoiceStateUpdateEventArgs e)
=> this._voiceStateUpdated.InvokeAsync(client, e);
/// <summary>
/// Handles the voice server update event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_VoiceServerUpdate(DiscordClient client, VoiceServerUpdateEventArgs e)
=> this._voiceServerUpdated.InvokeAsync(client, e);
/// <summary>
/// Handles the guild members chunk event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildMembersChunk(DiscordClient client, GuildMembersChunkEventArgs e)
=> this._guildMembersChunk.InvokeAsync(client, e);
/// <summary>
/// Handles the unknown events.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_UnknownEvent(DiscordClient client, UnknownEventArgs e)
=> this._unknownEvent.InvokeAsync(client, e);
/// <summary>
/// Handles the message reaction add event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_MessageReactionAdd(DiscordClient client, MessageReactionAddEventArgs e)
=> this._messageReactionAdded.InvokeAsync(client, e);
/// <summary>
/// Handles the message reaction remove event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_MessageReactionRemove(DiscordClient client, MessageReactionRemoveEventArgs e)
=> this._messageReactionRemoved.InvokeAsync(client, e);
/// <summary>
/// Handles the message reaction remove all event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_MessageReactionRemoveAll(DiscordClient client, MessageReactionsClearEventArgs e)
=> this._messageReactionsCleared.InvokeAsync(client, e);
/// <summary>
/// Handles the message reaction removed emoji event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_MessageReactionRemovedEmoji(DiscordClient client, MessageReactionRemoveEmojiEventArgs e)
=> this._messageReactionRemovedEmoji.InvokeAsync(client, e);
/// <summary>
/// Handles the interaction create event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_InteractionCreate(DiscordClient client, InteractionCreateEventArgs e)
=> this._interactionCreated.InvokeAsync(client, e);
/// <summary>
/// Handles the component interaction create event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_ComponentInteractionCreate(DiscordClient client, ComponentInteractionCreateEventArgs e)
=> this._componentInteractionCreated.InvokeAsync(client, e);
/// <summary>
/// Handles the context menu interaction create event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_ContextMenuInteractionCreate(DiscordClient client, ContextMenuInteractionCreateEventArgs e)
=> this._contextMenuInteractionCreated.InvokeAsync(client, e);
/// <summary>
/// Handles the webhooks update event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_WebhooksUpdate(DiscordClient client, WebhooksUpdateEventArgs e)
=> this._webhooksUpdated.InvokeAsync(client, e);
/// <summary>
/// Handles the heartbeated event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_HeartBeated(DiscordClient client, HeartbeatEventArgs e)
=> this._heartbeated.InvokeAsync(client, e);
/// <summary>
/// Handles the application command created event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_ApplicationCommandCreated(DiscordClient client, ApplicationCommandEventArgs e)
=> this._applicationCommandCreated.InvokeAsync(client, e);
/// <summary>
/// Handles the application command updated event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_ApplicationCommandUpdated(DiscordClient client, ApplicationCommandEventArgs e)
=> this._applicationCommandUpdated.InvokeAsync(client, e);
/// <summary>
/// Handles the application command deleted event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_ApplicationCommandDeleted(DiscordClient client, ApplicationCommandEventArgs e)
=> this._applicationCommandDeleted.InvokeAsync(client, e);
/// <summary>
/// Handles the guild application command count updated event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildApplicationCommandCountUpdated(DiscordClient client, GuildApplicationCommandCountEventArgs e)
=> this._guildApplicationCommandCountUpdated.InvokeAsync(client, e);
/// <summary>
/// Handles the application command permissions updated event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_ApplicationCommandPermissionsUpdated(DiscordClient client, ApplicationCommandPermissionsUpdateEventArgs e)
=> this._applicationCommandPermissionsUpdated.InvokeAsync(client, e);
/// <summary>
/// Handles the guild integration created event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildIntegrationCreated(DiscordClient client, GuildIntegrationCreateEventArgs e)
=> this._guildIntegrationCreated.InvokeAsync(client, e);
/// <summary>
/// Handles the guild integration updated event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildIntegrationUpdated(DiscordClient client, GuildIntegrationUpdateEventArgs e)
=> this._guildIntegrationUpdated.InvokeAsync(client, e);
/// <summary>
/// Handles the guild integration deleted event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildIntegrationDeleted(DiscordClient client, GuildIntegrationDeleteEventArgs e)
=> this._guildIntegrationDeleted.InvokeAsync(client, e);
/// <summary>
/// Handles the stage instance created event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_StageInstanceCreated(DiscordClient client, StageInstanceCreateEventArgs e)
=> this._stageInstanceCreated.InvokeAsync(client, e);
/// <summary>
/// Handles the stage instance updated event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_StageInstanceUpdated(DiscordClient client, StageInstanceUpdateEventArgs e)
=> this._stageInstanceUpdated.InvokeAsync(client, e);
/// <summary>
/// Handles the stage instance deleted event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_StageInstanceDeleted(DiscordClient client, StageInstanceDeleteEventArgs e)
=> this._stageInstanceDeleted.InvokeAsync(client, e);
/// <summary>
/// Handles the thread created event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_ThreadCreated(DiscordClient client, ThreadCreateEventArgs e)
=> this._threadCreated.InvokeAsync(client, e);
/// <summary>
/// Handles the thread updated event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_ThreadUpdated(DiscordClient client, ThreadUpdateEventArgs e)
=> this._threadUpdated.InvokeAsync(client, e);
/// <summary>
/// Handles the thread deleted event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_ThreadDeleted(DiscordClient client, ThreadDeleteEventArgs e)
=> this._threadDeleted.InvokeAsync(client, e);
/// <summary>
/// Handles the thread list synced event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_ThreadListSynced(DiscordClient client, ThreadListSyncEventArgs e)
=> this._threadListSynced.InvokeAsync(client, e);
/// <summary>
/// Handles the thread member updated event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_ThreadMemberUpdated(DiscordClient client, ThreadMemberUpdateEventArgs e)
=> this._threadMemberUpdated.InvokeAsync(client, e);
/// <summary>
/// Handles the thread members updated event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_ThreadMembersUpdated(DiscordClient client, ThreadMembersUpdateEventArgs e)
=> this._threadMembersUpdated.InvokeAsync(client, e);
/// <summary>
/// Handles the scheduled event created event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildScheduledEventCreated(DiscordClient client, GuildScheduledEventCreateEventArgs e)
=> this._guildScheduledEventCreated.InvokeAsync(client, e);
/// <summary>
/// Handles the scheduled event updated event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildScheduledEventUpdated(DiscordClient client, GuildScheduledEventUpdateEventArgs e)
=> this._guildScheduledEventUpdated.InvokeAsync(client, e);
/// <summary>
/// Handles the scheduled event deleted event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildScheduledEventDeleted(DiscordClient client, GuildScheduledEventDeleteEventArgs e)
=> this._guildScheduledEventDeleted.InvokeAsync(client, e);
/// <summary>
/// Handles the scheduled event user added event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildScheduledEventUserAdded(DiscordClient client, GuildScheduledEventUserAddEventArgs e)
=> this._guildScheduledEventUserAdded.InvokeAsync(client, e);
/// <summary>
/// Handles the scheduled event user removed event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildScheduledEventUserRemoved(DiscordClient client, GuildScheduledEventUserRemoveEventArgs e)
=> this._guildScheduledEventUserRemoved.InvokeAsync(client, e);
/// <summary>
/// Handles the automod rule created event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_AutomodRuleCreated(DiscordClient client, AutomodRuleCreateEventArgs e)
=> this._automodRuleCreated.InvokeAsync(client, e);
/// <summary>
/// Handles the automod rule updated event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_AutomodRuleUpdated(DiscordClient client, AutomodRuleUpdateEventArgs e)
=> this._automodRuleUpdated.InvokeAsync(client, e);
/// <summary>
/// Handles the automod rule deleted event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_AutomodRuleDeleted(DiscordClient client, AutomodRuleDeleteEventArgs e)
=> this._automodRuleDeleted.InvokeAsync(client, e);
/// <summary>
/// Handles the automod action executed event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_AutomodActionExecuted(DiscordClient client, AutomodActionExecutedEventArgs e)
=> this._automodActionExecuted.InvokeAsync(client, e);
/// <summary>
/// Handles the guild audit log created event.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="e">The event args.</param>
private Task Client_GuildAuditLogEntryCreated(DiscordClient client, GuildAuditLogEntryCreateEventArgs e)
=> this._guildAuditLogEntryCreated.InvokeAsync(client, e);
#endregion
}
diff --git a/DisCatSharp/Clients/DiscordShardedClient.cs b/DisCatSharp/Clients/DiscordShardedClient.cs
index a898f1895..9af2d9d4e 100644
--- a/DisCatSharp/Clients/DiscordShardedClient.cs
+++ b/DisCatSharp/Clients/DiscordShardedClient.cs
@@ -1,794 +1,794 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// A Discord client that shards automatically.
/// </summary>
public sealed partial class DiscordShardedClient
{
#region Public Properties
/// <summary>
/// Gets the logger for this client.
/// </summary>
public ILogger<BaseDiscordClient> Logger { get; }
/// <summary>
/// Gets all client shards.
/// </summary>
public IReadOnlyDictionary<int, DiscordClient> ShardClients { get; }
/// <summary>
/// Gets the gateway info for the client's session.
/// </summary>
public GatewayInfo GatewayInfo { get; private set; }
/// <summary>
/// Gets the current user.
/// </summary>
public DiscordUser CurrentUser { get; private set; }
/// <summary>
/// Gets the current application.
/// </summary>
public DiscordApplication CurrentApplication { get; private set; }
/// <summary>
/// Gets the list of available voice regions. Note that this property will not contain VIP voice regions.
/// </summary>
public IReadOnlyDictionary<string, DiscordVoiceRegion> VoiceRegions
=> this._voiceRegionsLazy?.Value;
#endregion
#region Private Properties/Fields
/// <summary>
/// Gets the configuration.
/// </summary>
private readonly DiscordConfiguration _configuration;
/// <summary>
/// Gets the list of available voice regions. This property is meant as a way to modify <see cref="VoiceRegions"/>.
/// </summary>
private ConcurrentDictionary<string, DiscordVoiceRegion> _internalVoiceRegions;
/// <summary>
/// Gets a list of shards.
/// </summary>
private readonly ConcurrentDictionary<int, DiscordClient> _shards = new();
/// <summary>
/// Gets a lazy list of voice regions.
/// </summary>
private Lazy<IReadOnlyDictionary<string, DiscordVoiceRegion>> _voiceRegionsLazy;
/// <summary>
/// Whether the shard client is started.
/// </summary>
private bool _isStarted;
/// <summary>
/// Whether manual sharding is enabled.
/// </summary>
private readonly bool _manuallySharding;
#endregion
#region Constructor
/// <summary>
/// Initializes a new auto-sharding Discord client.
/// </summary>
/// <param name="config">The configuration to use.</param>
public DiscordShardedClient(DiscordConfiguration config)
{
this.InternalSetup();
if (config.ShardCount > 1)
this._manuallySharding = true;
this._configuration = config;
this.ShardClients = new ReadOnlyConcurrentDictionary<int, DiscordClient>(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<BaseDiscordClient>();
}
#endregion
#region Public Methods
/// <summary>
/// Initializes and connects all shards.
/// </summary>
/// <exception cref="AggregateException"></exception>
/// <exception cref="InvalidOperationException"></exception>
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<Task>();
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);
}
}
/// <summary>
/// Disconnects and disposes all shards.
/// </summary>
/// <exception cref="InvalidOperationException"></exception>
public Task StopAsync()
=> this.InternalStopAsync();
/// <summary>
/// Gets a shard from a guild id.
/// <para>
/// If automatically sharding, this will use the <see cref="Utilities.GetShardId(ulong, int)"/> method.
/// Otherwise if manually sharding, it will instead iterate through each shard's guild caches.
/// </para>
/// </summary>
/// <param name="guildId">The guild ID for the shard.</param>
/// <returns>The found <see cref="DiscordClient"/> shard. Otherwise null if the shard was not found for the guild id.</returns>
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;
}
/// <summary>
/// Gets a shard from a guild.
/// <para>
/// If automatically sharding, this will use the <see cref="Utilities.GetShardId(ulong, int)"/> method.
/// Otherwise if manually sharding, it will instead iterate through each shard's guild caches.
/// </para>
/// </summary>
/// <param name="guild">The guild for the shard.</param>
/// <returns>The found <see cref="DiscordClient"/> shard. Otherwise null if the shard was not found for the guild.</returns>
public DiscordClient GetShard(DiscordGuild guild)
=> this.GetShard(guild.Id);
/// <summary>
/// Updates the status on all shards.
/// </summary>
/// <param name="activity">The activity to set. Defaults to null.</param>
/// <param name="userStatus">The optional status to set. Defaults to null.</param>
/// <param name="idleSince">Since when is the client performing the specified activity. Defaults to null.</param>
/// <returns>Asynchronous operation.</returns>
public async Task UpdateStatusAsync(DiscordActivity activity = null, UserStatus? userStatus = null, DateTimeOffset? idleSince = null)
{
var tasks = new List<Task>();
foreach (var client in this._shards.Values)
tasks.Add(client.UpdateStatusAsync(activity, userStatus, idleSince));
await Task.WhenAll(tasks).ConfigureAwait(false);
}
/// <summary>
/// <see cref="BaseDiscordClient.GetLibraryDevelopmentTeamAsync"/>
/// </summary>
[Obsolete("Don't use this right now, inactive")]
public async Task<DisCatSharpTeam> GetLibraryDevelopmentTeamAsync()
=> await this.GetShard(0).GetLibraryDevelopmentTeamAsync().ConfigureAwait(false);
#endregion
#region Internal Methods
/// <summary>
/// Initializes the shards.
/// </summary>
/// <returns>The count of initialized shards.</returns>
internal async Task<int> InitializeShardsAsync()
{
if (!this._shards.IsEmpty)
return this._shards.Count;
this.GatewayInfo = await this.GetGatewayInfoAsync().ConfigureAwait(false);
var shardCount = this._configuration.ShardCount == 1 ? this.GatewayInfo.ShardCount : this._configuration.ShardCount;
var lf = new ShardedLoggerFactory(this.Logger);
for (var i = 0; i < shardCount; i++)
{
var cfg = new DiscordConfiguration(this._configuration)
{
ShardId = i,
ShardCount = shardCount,
LoggerFactory = lf
};
var client = new DiscordClient(cfg);
if (!this._shards.TryAdd(i, client))
throw new InvalidOperationException("Could not initialize shards.");
}
return shardCount;
}
#endregion
#region Private Methods & Version Property
/// <summary>
/// Gets the gateway info.
/// </summary>
private async Task<GatewayInfo> 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));
http.DefaultRequestHeaders.TryAddWithoutValidation("X-Discord-Locale", this._configuration.Locale);
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<GatewayInfo>();
//There is a delay from parsing here.
timer.Stop();
info.SessionBucket.ResetAfterInternal -= (int)timer.ElapsedMilliseconds;
info.SessionBucket.ResetAfter = DateTimeOffset.UtcNow + TimeSpan.FromMilliseconds(info.SessionBucket.ResetAfterInternal);
return info;
async Task<bool> HandleHttpError(string reqUrl, HttpResponseMessage msg)
{
var code = (int)msg.StatusCode;
if (code == 401 || code == 403)
{
throw new Exception($"Authentication failed, check your token and try again: {code} {msg.ReasonPhrase}");
}
else if (code == 429)
{
this.Logger.LogError(LoggerEvents.ShardClientError, $"Ratelimit hit, requeuing request to {reqUrl}");
var hs = msg.Headers.ToDictionary(xh => xh.Key, xh => string.Join("\n", xh.Value), StringComparer.OrdinalIgnoreCase);
var waitInterval = 0;
if (hs.TryGetValue("Retry-After", out var retryAfterRaw))
waitInterval = int.Parse(retryAfterRaw, CultureInfo.InvariantCulture);
await Task.Delay(waitInterval).ConfigureAwait(false);
return true;
}
else if (code >= 500)
{
throw new Exception($"Internal Server Error: {code} {msg.ReasonPhrase}");
}
else
{
throw new Exception($"An unsuccessful HTTP status code was encountered: {code} {msg.ReasonPhrase}");
}
}
}
/// <summary>
/// Gets the version string.
/// </summary>
private readonly Lazy<string> _versionString = new(() =>
{
var a = typeof(DiscordShardedClient).GetTypeInfo().Assembly;
var iv = a.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
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;
});
/// <summary>
/// Gets the name of the used bot library.
/// </summary>
private readonly string _botLibrary = "DisCatSharp";
#endregion
#region Private Connection Methods
/// <summary>
/// Connects a shard.
/// </summary>
/// <param name="i">The shard id.</param>
private async Task ConnectShardAsync(int i)
{
if (!this._shards.TryGetValue(i, out var client))
throw new Exception($"Could not initialize shard {i}.");
client.IsShard = true;
if (this.GatewayInfo != null)
{
client.GatewayInfo = this.GatewayInfo;
client.GatewayUri = new Uri(client.GatewayInfo.Url);
}
if (this.CurrentUser != null)
client.CurrentUser = this.CurrentUser;
if (this.CurrentApplication != null)
client.CurrentApplication = this.CurrentApplication;
if (this._internalVoiceRegions != null)
{
client.InternalVoiceRegions = this._internalVoiceRegions;
client.VoiceRegionsLazy = new Lazy<IReadOnlyDictionary<string, DiscordVoiceRegion>>(() => new ReadOnlyDictionary<string, DiscordVoiceRegion>(client.InternalVoiceRegions));
}
this.HookEventHandlers(client);
await client.ConnectAsync();
this.Logger.LogInformation(LoggerEvents.ShardStartup, "Booted shard {0}.", i);
this.GatewayInfo ??= client.GatewayInfo;
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<IReadOnlyDictionary<string, DiscordVoiceRegion>>(() => new ReadOnlyDictionary<string, DiscordVoiceRegion>(this._internalVoiceRegions));
}
}
/// <summary>
/// Stops all shards.
/// </summary>
/// <param name="enableLogger">Whether to enable the logger.</param>
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
/// <summary>
/// Sets the shard client up internally..
/// </summary>
private void InternalSetup()
{
this._clientErrored = new AsyncEvent<DiscordClient, ClientErrorEventArgs>("CLIENT_ERRORED", DiscordClient.EventExecutionLimit, this.Goof);
this._socketErrored = new AsyncEvent<DiscordClient, SocketErrorEventArgs>("SOCKET_ERRORED", DiscordClient.EventExecutionLimit, this.Goof);
this._socketOpened = new AsyncEvent<DiscordClient, SocketEventArgs>("SOCKET_OPENED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._socketClosed = new AsyncEvent<DiscordClient, SocketCloseEventArgs>("SOCKET_CLOSED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._ready = new AsyncEvent<DiscordClient, ReadyEventArgs>("READY", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._resumed = new AsyncEvent<DiscordClient, ReadyEventArgs>("RESUMED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._channelCreated = new AsyncEvent<DiscordClient, ChannelCreateEventArgs>("CHANNEL_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._channelUpdated = new AsyncEvent<DiscordClient, ChannelUpdateEventArgs>("CHANNEL_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._channelDeleted = new AsyncEvent<DiscordClient, ChannelDeleteEventArgs>("CHANNEL_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._dmChannelDeleted = new AsyncEvent<DiscordClient, DmChannelDeleteEventArgs>("DM_CHANNEL_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._channelPinsUpdated = new AsyncEvent<DiscordClient, ChannelPinsUpdateEventArgs>("CHANNEL_PINS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildCreated = new AsyncEvent<DiscordClient, GuildCreateEventArgs>("GUILD_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildAvailable = new AsyncEvent<DiscordClient, GuildCreateEventArgs>("GUILD_AVAILABLE", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildUpdated = new AsyncEvent<DiscordClient, GuildUpdateEventArgs>("GUILD_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildDeleted = new AsyncEvent<DiscordClient, GuildDeleteEventArgs>("GUILD_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildUnavailable = new AsyncEvent<DiscordClient, GuildDeleteEventArgs>("GUILD_UNAVAILABLE", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildDownloadCompleted = new AsyncEvent<DiscordClient, GuildDownloadCompletedEventArgs>("GUILD_DOWNLOAD_COMPLETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._inviteCreated = new AsyncEvent<DiscordClient, InviteCreateEventArgs>("INVITE_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._inviteDeleted = new AsyncEvent<DiscordClient, InviteDeleteEventArgs>("INVITE_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._messageCreated = new AsyncEvent<DiscordClient, MessageCreateEventArgs>("MESSAGE_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._presenceUpdated = new AsyncEvent<DiscordClient, PresenceUpdateEventArgs>("PRESENCE_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildBanAdded = new AsyncEvent<DiscordClient, GuildBanAddEventArgs>("GUILD_BAN_ADDED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildBanRemoved = new AsyncEvent<DiscordClient, GuildBanRemoveEventArgs>("GUILD_BAN_REMOVED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildEmojisUpdated = new AsyncEvent<DiscordClient, GuildEmojisUpdateEventArgs>("GUILD_EMOJI_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildStickersUpdated = new AsyncEvent<DiscordClient, GuildStickersUpdateEventArgs>("GUILD_STICKER_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildIntegrationsUpdated = new AsyncEvent<DiscordClient, GuildIntegrationsUpdateEventArgs>("GUILD_INTEGRATIONS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildMemberAdded = new AsyncEvent<DiscordClient, GuildMemberAddEventArgs>("GUILD_MEMBER_ADDED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildMemberRemoved = new AsyncEvent<DiscordClient, GuildMemberRemoveEventArgs>("GUILD_MEMBER_REMOVED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildMemberUpdated = new AsyncEvent<DiscordClient, GuildMemberUpdateEventArgs>("GUILD_MEMBER_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildRoleCreated = new AsyncEvent<DiscordClient, GuildRoleCreateEventArgs>("GUILD_ROLE_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildRoleUpdated = new AsyncEvent<DiscordClient, GuildRoleUpdateEventArgs>("GUILD_ROLE_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildRoleDeleted = new AsyncEvent<DiscordClient, GuildRoleDeleteEventArgs>("GUILD_ROLE_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._messageUpdated = new AsyncEvent<DiscordClient, MessageUpdateEventArgs>("MESSAGE_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._messageDeleted = new AsyncEvent<DiscordClient, MessageDeleteEventArgs>("MESSAGE_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._messageBulkDeleted = new AsyncEvent<DiscordClient, MessageBulkDeleteEventArgs>("MESSAGE_BULK_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._interactionCreated = new AsyncEvent<DiscordClient, InteractionCreateEventArgs>("INTERACTION_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._componentInteractionCreated = new AsyncEvent<DiscordClient, ComponentInteractionCreateEventArgs>("COMPONENT_INTERACTED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._contextMenuInteractionCreated = new AsyncEvent<DiscordClient, ContextMenuInteractionCreateEventArgs>("CONTEXT_MENU_INTERACTED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._typingStarted = new AsyncEvent<DiscordClient, TypingStartEventArgs>("TYPING_STARTED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._userSettingsUpdated = new AsyncEvent<DiscordClient, UserSettingsUpdateEventArgs>("USER_SETTINGS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._userUpdated = new AsyncEvent<DiscordClient, UserUpdateEventArgs>("USER_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._voiceStateUpdated = new AsyncEvent<DiscordClient, VoiceStateUpdateEventArgs>("VOICE_STATE_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._voiceServerUpdated = new AsyncEvent<DiscordClient, VoiceServerUpdateEventArgs>("VOICE_SERVER_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildMembersChunk = new AsyncEvent<DiscordClient, GuildMembersChunkEventArgs>("GUILD_MEMBERS_CHUNKED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._unknownEvent = new AsyncEvent<DiscordClient, UnknownEventArgs>("UNKNOWN_EVENT", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._messageReactionAdded = new AsyncEvent<DiscordClient, MessageReactionAddEventArgs>("MESSAGE_REACTION_ADDED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._messageReactionRemoved = new AsyncEvent<DiscordClient, MessageReactionRemoveEventArgs>("MESSAGE_REACTION_REMOVED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._messageReactionsCleared = new AsyncEvent<DiscordClient, MessageReactionsClearEventArgs>("MESSAGE_REACTIONS_CLEARED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._messageReactionRemovedEmoji = new AsyncEvent<DiscordClient, MessageReactionRemoveEmojiEventArgs>("MESSAGE_REACTION_REMOVED_EMOJI", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._webhooksUpdated = new AsyncEvent<DiscordClient, WebhooksUpdateEventArgs>("WEBHOOKS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._heartbeated = new AsyncEvent<DiscordClient, HeartbeatEventArgs>("HEARTBEATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._applicationCommandCreated = new AsyncEvent<DiscordClient, ApplicationCommandEventArgs>("APPLICATION_COMMAND_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._applicationCommandUpdated = new AsyncEvent<DiscordClient, ApplicationCommandEventArgs>("APPLICATION_COMMAND_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._applicationCommandDeleted = new AsyncEvent<DiscordClient, ApplicationCommandEventArgs>("APPLICATION_COMMAND_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildApplicationCommandCountUpdated = new AsyncEvent<DiscordClient, GuildApplicationCommandCountEventArgs>("GUILD_APPLICATION_COMMAND_COUNTS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._applicationCommandPermissionsUpdated = new AsyncEvent<DiscordClient, ApplicationCommandPermissionsUpdateEventArgs>("APPLICATION_COMMAND_PERMISSIONS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildIntegrationCreated = new AsyncEvent<DiscordClient, GuildIntegrationCreateEventArgs>("INTEGRATION_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildIntegrationUpdated = new AsyncEvent<DiscordClient, GuildIntegrationUpdateEventArgs>("INTEGRATION_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildIntegrationDeleted = new AsyncEvent<DiscordClient, GuildIntegrationDeleteEventArgs>("INTEGRATION_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._stageInstanceCreated = new AsyncEvent<DiscordClient, StageInstanceCreateEventArgs>("STAGE_INSTANCE_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._stageInstanceUpdated = new AsyncEvent<DiscordClient, StageInstanceUpdateEventArgs>("STAGE_INSTANCE_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._stageInstanceDeleted = new AsyncEvent<DiscordClient, StageInstanceDeleteEventArgs>("STAGE_INSTANCE_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._threadCreated = new AsyncEvent<DiscordClient, ThreadCreateEventArgs>("THREAD_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._threadUpdated = new AsyncEvent<DiscordClient, ThreadUpdateEventArgs>("THREAD_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._threadDeleted = new AsyncEvent<DiscordClient, ThreadDeleteEventArgs>("THREAD_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._threadListSynced = new AsyncEvent<DiscordClient, ThreadListSyncEventArgs>("THREAD_LIST_SYNCED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._threadMemberUpdated = new AsyncEvent<DiscordClient, ThreadMemberUpdateEventArgs>("THREAD_MEMBER_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._threadMembersUpdated = new AsyncEvent<DiscordClient, ThreadMembersUpdateEventArgs>("THREAD_MEMBERS_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._zombied = new AsyncEvent<DiscordClient, ZombiedEventArgs>("ZOMBIED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._payloadReceived = new AsyncEvent<DiscordClient, PayloadReceivedEventArgs>("PAYLOAD_RECEIVED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildScheduledEventCreated = new AsyncEvent<DiscordClient, GuildScheduledEventCreateEventArgs>("GUILD_SCHEDULED_EVENT_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildScheduledEventUpdated = new AsyncEvent<DiscordClient, GuildScheduledEventUpdateEventArgs>("GUILD_SCHEDULED_EVENT_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildScheduledEventDeleted = new AsyncEvent<DiscordClient, GuildScheduledEventDeleteEventArgs>("GUILD_SCHEDULED_EVENT_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildScheduledEventUserAdded = new AsyncEvent<DiscordClient, GuildScheduledEventUserAddEventArgs>("GUILD_SCHEDULED_EVENT_USER_ADDED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildScheduledEventUserRemoved = new AsyncEvent<DiscordClient, GuildScheduledEventUserRemoveEventArgs>("GUILD_SCHEDULED_EVENT_USER_REMOVED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._embeddedActivityUpdated = new AsyncEvent<DiscordClient, EmbeddedActivityUpdateEventArgs>("EMBEDDED_ACTIVITY_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildMemberTimeoutAdded = new AsyncEvent<DiscordClient, GuildMemberTimeoutAddEventArgs>("GUILD_MEMBER_TIMEOUT_ADDED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildMemberTimeoutChanged = new AsyncEvent<DiscordClient, GuildMemberTimeoutUpdateEventArgs>("GUILD_MEMBER_TIMEOUT_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildMemberTimeoutRemoved = new AsyncEvent<DiscordClient, GuildMemberTimeoutRemoveEventArgs>("GUILD_MEMBER_TIMEOUT_REMOVED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._automodRuleCreated = new AsyncEvent<DiscordClient, AutomodRuleCreateEventArgs>("AUTO_MODERATION_RULE_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._automodRuleUpdated = new AsyncEvent<DiscordClient, AutomodRuleUpdateEventArgs>("AUTO_MODERATION_RULE_UPDATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._automodRuleDeleted = new AsyncEvent<DiscordClient, AutomodRuleDeleteEventArgs>("AUTO_MODERATION_RULE_DELETED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._automodActionExecuted = new AsyncEvent<DiscordClient, AutomodActionExecutedEventArgs>("AUTO_MODERATION_ACTION_EXECUTED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
this._guildAuditLogEntryCreated = new AsyncEvent<DiscordClient, GuildAuditLogEntryCreateEventArgs>("GUILD_AUDIT_LOG_ENTRY_CREATED", DiscordClient.EventExecutionLimit, this.EventErrorHandler);
}
/// <summary>
/// Hooks the event handlers.
/// </summary>
/// <param name="client">The client.</param>
private void HookEventHandlers(DiscordClient client)
{
client.ClientErrored += this.Client_ClientError;
client.SocketErrored += this.Client_SocketError;
client.SocketOpened += this.Client_SocketOpened;
client.SocketClosed += this.Client_SocketClosed;
client.Ready += this.Client_Ready;
client.Resumed += this.Client_Resumed;
client.ChannelCreated += this.Client_ChannelCreated;
client.ChannelUpdated += this.Client_ChannelUpdated;
client.ChannelDeleted += this.Client_ChannelDeleted;
client.DmChannelDeleted += this.Client_DMChannelDeleted;
client.ChannelPinsUpdated += this.Client_ChannelPinsUpdated;
client.GuildCreated += this.Client_GuildCreated;
client.GuildAvailable += this.Client_GuildAvailable;
client.GuildUpdated += this.Client_GuildUpdated;
client.GuildDeleted += this.Client_GuildDeleted;
client.GuildUnavailable += this.Client_GuildUnavailable;
client.GuildDownloadCompleted += this.Client_GuildDownloadCompleted;
client.InviteCreated += this.Client_InviteCreated;
client.InviteDeleted += this.Client_InviteDeleted;
client.MessageCreated += this.Client_MessageCreated;
client.PresenceUpdated += this.Client_PresenceUpdate;
client.GuildBanAdded += this.Client_GuildBanAdd;
client.GuildBanRemoved += this.Client_GuildBanRemove;
client.GuildEmojisUpdated += this.Client_GuildEmojisUpdate;
client.GuildStickersUpdated += this.Client_GuildStickersUpdate;
client.GuildIntegrationsUpdated += this.Client_GuildIntegrationsUpdate;
client.GuildMemberAdded += this.Client_GuildMemberAdd;
client.GuildMemberRemoved += this.Client_GuildMemberRemove;
client.GuildMemberUpdated += this.Client_GuildMemberUpdate;
client.GuildRoleCreated += this.Client_GuildRoleCreate;
client.GuildRoleUpdated += this.Client_GuildRoleUpdate;
client.GuildRoleDeleted += this.Client_GuildRoleDelete;
client.MessageUpdated += this.Client_MessageUpdate;
client.MessageDeleted += this.Client_MessageDelete;
client.MessagesBulkDeleted += this.Client_MessageBulkDelete;
client.InteractionCreated += this.Client_InteractionCreate;
client.ComponentInteractionCreated += this.Client_ComponentInteractionCreate;
client.ContextMenuInteractionCreated += this.Client_ContextMenuInteractionCreate;
client.TypingStarted += this.Client_TypingStart;
client.UserSettingsUpdated += this.Client_UserSettingsUpdate;
client.UserUpdated += this.Client_UserUpdate;
client.VoiceStateUpdated += this.Client_VoiceStateUpdate;
client.VoiceServerUpdated += this.Client_VoiceServerUpdate;
client.GuildMembersChunked += this.Client_GuildMembersChunk;
client.UnknownEvent += this.Client_UnknownEvent;
client.MessageReactionAdded += this.Client_MessageReactionAdd;
client.MessageReactionRemoved += this.Client_MessageReactionRemove;
client.MessageReactionsCleared += this.Client_MessageReactionRemoveAll;
client.MessageReactionRemovedEmoji += this.Client_MessageReactionRemovedEmoji;
client.WebhooksUpdated += this.Client_WebhooksUpdate;
client.Heartbeated += this.Client_HeartBeated;
client.ApplicationCommandCreated += this.Client_ApplicationCommandCreated;
client.ApplicationCommandUpdated += this.Client_ApplicationCommandUpdated;
client.ApplicationCommandDeleted += this.Client_ApplicationCommandDeleted;
client.GuildApplicationCommandCountUpdated += this.Client_GuildApplicationCommandCountUpdated;
client.ApplicationCommandPermissionsUpdated += this.Client_ApplicationCommandPermissionsUpdated;
client.GuildIntegrationCreated += this.Client_GuildIntegrationCreated;
client.GuildIntegrationUpdated += this.Client_GuildIntegrationUpdated;
client.GuildIntegrationDeleted += this.Client_GuildIntegrationDeleted;
client.StageInstanceCreated += this.Client_StageInstanceCreated;
client.StageInstanceUpdated += this.Client_StageInstanceUpdated;
client.StageInstanceDeleted += this.Client_StageInstanceDeleted;
client.ThreadCreated += this.Client_ThreadCreated;
client.ThreadUpdated += this.Client_ThreadUpdated;
client.ThreadDeleted += this.Client_ThreadDeleted;
client.ThreadListSynced += this.Client_ThreadListSynced;
client.ThreadMemberUpdated += this.Client_ThreadMemberUpdated;
client.ThreadMembersUpdated += this.Client_ThreadMembersUpdated;
client.Zombied += this.Client_Zombied;
client.PayloadReceived += this.Client_PayloadReceived;
client.GuildScheduledEventCreated += this.Client_GuildScheduledEventCreated;
client.GuildScheduledEventUpdated += this.Client_GuildScheduledEventUpdated;
client.GuildScheduledEventDeleted += this.Client_GuildScheduledEventDeleted;
client.GuildScheduledEventUserAdded += this.Client_GuildScheduledEventUserAdded; ;
client.GuildScheduledEventUserRemoved += this.Client_GuildScheduledEventUserRemoved;
client.EmbeddedActivityUpdated += this.Client_EmbeddedActivityUpdated;
client.GuildMemberTimeoutAdded += this.Client_GuildMemberTimeoutAdded;
client.GuildMemberTimeoutChanged += this.Client_GuildMemberTimeoutChanged;
client.GuildMemberTimeoutRemoved += this.Client_GuildMemberTimeoutRemoved;
client.AutomodRuleCreated += this.Client_AutomodRuleCreated;
client.AutomodRuleUpdated += this.Client_AutomodRuleUpdated;
client.AutomodRuleDeleted += this.Client_AutomodRuleDeleted;
client.AutomodActionExecuted += this.Client_AutomodActionExecuted;
client.GuildAuditLogEntryCreated += this.Client_GuildAuditLogEntryCreated;
}
/// <summary>
/// Unhooks the event handlers.
/// </summary>
/// <param name="client">The client.</param>
private void UnhookEventHandlers(DiscordClient client)
{
client.ClientErrored -= this.Client_ClientError;
client.SocketErrored -= this.Client_SocketError;
client.SocketOpened -= this.Client_SocketOpened;
client.SocketClosed -= this.Client_SocketClosed;
client.Ready -= this.Client_Ready;
client.Resumed -= this.Client_Resumed;
client.ChannelCreated -= this.Client_ChannelCreated;
client.ChannelUpdated -= this.Client_ChannelUpdated;
client.ChannelDeleted -= this.Client_ChannelDeleted;
client.DmChannelDeleted -= this.Client_DMChannelDeleted;
client.ChannelPinsUpdated -= this.Client_ChannelPinsUpdated;
client.GuildCreated -= this.Client_GuildCreated;
client.GuildAvailable -= this.Client_GuildAvailable;
client.GuildUpdated -= this.Client_GuildUpdated;
client.GuildDeleted -= this.Client_GuildDeleted;
client.GuildUnavailable -= this.Client_GuildUnavailable;
client.GuildDownloadCompleted -= this.Client_GuildDownloadCompleted;
client.InviteCreated -= this.Client_InviteCreated;
client.InviteDeleted -= this.Client_InviteDeleted;
client.MessageCreated -= this.Client_MessageCreated;
client.PresenceUpdated -= this.Client_PresenceUpdate;
client.GuildBanAdded -= this.Client_GuildBanAdd;
client.GuildBanRemoved -= this.Client_GuildBanRemove;
client.GuildEmojisUpdated -= this.Client_GuildEmojisUpdate;
client.GuildStickersUpdated -= this.Client_GuildStickersUpdate;
client.GuildIntegrationsUpdated -= this.Client_GuildIntegrationsUpdate;
client.GuildMemberAdded -= this.Client_GuildMemberAdd;
client.GuildMemberRemoved -= this.Client_GuildMemberRemove;
client.GuildMemberUpdated -= this.Client_GuildMemberUpdate;
client.GuildRoleCreated -= this.Client_GuildRoleCreate;
client.GuildRoleUpdated -= this.Client_GuildRoleUpdate;
client.GuildRoleDeleted -= this.Client_GuildRoleDelete;
client.MessageUpdated -= this.Client_MessageUpdate;
client.MessageDeleted -= this.Client_MessageDelete;
client.MessagesBulkDeleted -= this.Client_MessageBulkDelete;
client.InteractionCreated -= this.Client_InteractionCreate;
client.ComponentInteractionCreated -= this.Client_ComponentInteractionCreate;
client.ContextMenuInteractionCreated -= this.Client_ContextMenuInteractionCreate;
client.TypingStarted -= this.Client_TypingStart;
client.UserSettingsUpdated -= this.Client_UserSettingsUpdate;
client.UserUpdated -= this.Client_UserUpdate;
client.VoiceStateUpdated -= this.Client_VoiceStateUpdate;
client.VoiceServerUpdated -= this.Client_VoiceServerUpdate;
client.GuildMembersChunked -= this.Client_GuildMembersChunk;
client.UnknownEvent -= this.Client_UnknownEvent;
client.MessageReactionAdded -= this.Client_MessageReactionAdd;
client.MessageReactionRemoved -= this.Client_MessageReactionRemove;
client.MessageReactionsCleared -= this.Client_MessageReactionRemoveAll;
client.MessageReactionRemovedEmoji -= this.Client_MessageReactionRemovedEmoji;
client.WebhooksUpdated -= this.Client_WebhooksUpdate;
client.Heartbeated -= this.Client_HeartBeated;
client.ApplicationCommandCreated -= this.Client_ApplicationCommandCreated;
client.ApplicationCommandUpdated -= this.Client_ApplicationCommandUpdated;
client.ApplicationCommandDeleted -= this.Client_ApplicationCommandDeleted;
client.GuildApplicationCommandCountUpdated -= this.Client_GuildApplicationCommandCountUpdated;
client.ApplicationCommandPermissionsUpdated -= this.Client_ApplicationCommandPermissionsUpdated;
client.GuildIntegrationCreated -= this.Client_GuildIntegrationCreated;
client.GuildIntegrationUpdated -= this.Client_GuildIntegrationUpdated;
client.GuildIntegrationDeleted -= this.Client_GuildIntegrationDeleted;
client.StageInstanceCreated -= this.Client_StageInstanceCreated;
client.StageInstanceUpdated -= this.Client_StageInstanceUpdated;
client.StageInstanceDeleted -= this.Client_StageInstanceDeleted;
client.ThreadCreated -= this.Client_ThreadCreated;
client.ThreadUpdated -= this.Client_ThreadUpdated;
client.ThreadDeleted -= this.Client_ThreadDeleted;
client.ThreadListSynced -= this.Client_ThreadListSynced;
client.ThreadMemberUpdated -= this.Client_ThreadMemberUpdated;
client.ThreadMembersUpdated -= this.Client_ThreadMembersUpdated;
client.Zombied -= this.Client_Zombied;
client.PayloadReceived -= this.Client_PayloadReceived;
client.GuildScheduledEventCreated -= this.Client_GuildScheduledEventCreated;
client.GuildScheduledEventUpdated -= this.Client_GuildScheduledEventUpdated;
client.GuildScheduledEventDeleted -= this.Client_GuildScheduledEventDeleted;
client.GuildScheduledEventUserAdded -= this.Client_GuildScheduledEventUserAdded; ;
client.GuildScheduledEventUserRemoved -= this.Client_GuildScheduledEventUserRemoved;
client.EmbeddedActivityUpdated -= this.Client_EmbeddedActivityUpdated;
client.GuildMemberTimeoutAdded -= this.Client_GuildMemberTimeoutAdded;
client.GuildMemberTimeoutChanged -= this.Client_GuildMemberTimeoutChanged;
client.GuildMemberTimeoutRemoved -= this.Client_GuildMemberTimeoutRemoved;
client.AutomodRuleCreated -= this.Client_AutomodRuleCreated;
client.AutomodRuleUpdated -= this.Client_AutomodRuleUpdated;
client.AutomodRuleDeleted -= this.Client_AutomodRuleDeleted;
client.AutomodActionExecuted -= this.Client_AutomodActionExecuted;
client.GuildAuditLogEntryCreated -= this.Client_GuildAuditLogEntryCreated;
}
/// <summary>
/// Gets the shard id from guilds.
/// </summary>
/// <param name="id">The id.</param>
/// <returns>An int.</returns>
private int GetShardIdFromGuilds(ulong id)
{
foreach (var s in this._shards.Values)
{
if (s.GuildsInternal.TryGetValue(id, out _))
{
return s.ShardId;
}
}
return -1;
}
#endregion
#region Destructor
~DiscordShardedClient()
{
this.InternalStopAsync(false).GetAwaiter().GetResult();
}
#endregion
}
diff --git a/DisCatSharp/Clients/DiscordWebhookClient.cs b/DisCatSharp/Clients/DiscordWebhookClient.cs
index 1c95f4ba8..073f3b085 100644
--- a/DisCatSharp/Clients/DiscordWebhookClient.cs
+++ b/DisCatSharp/Clients/DiscordWebhookClient.cs
@@ -1,255 +1,255 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a webhook-only client. This client can be used to execute Discord Webhooks.
/// </summary>
public class DiscordWebhookClient
{
/// <summary>
/// Gets the logger for this client.
/// </summary>
public ILogger<DiscordWebhookClient> Logger { get; }
/// <summary>
/// Gets the webhook regex.
/// This regex has 2 named capture groups: "id" and "token".
/// </summary>
private static Regex s_webhookRegex { get; } = new(@"(?:https?:\/\/)?discord(?:app)?.com\/api\/(?:v\d\/)?webhooks\/(?<id>\d+)\/(?<token>[A-Za-z0-9_\-]+)", RegexOptions.ECMAScript);
/// <summary>
/// Gets the collection of registered webhooks.
/// </summary>
public IReadOnlyList<DiscordWebhook> Webhooks { get; }
/// <summary>
/// Gets or sets the username for registered webhooks. Note that this only takes effect when broadcasting.
/// </summary>
public string Username { get; set; }
/// <summary>
/// Gets or set the avatar for registered webhooks. Note that this only takes effect when broadcasting.
/// </summary>
public string AvatarUrl { get; set; }
internal List<DiscordWebhook> Hooks;
internal DiscordApiClient Apiclient;
internal LogLevel MinimumLogLevel;
internal string LogTimestampFormat;
/// <summary>
/// Creates a new webhook client.
/// </summary>
public DiscordWebhookClient()
: this(null, null)
{ }
/// <summary>
/// Creates a new webhook client, with specified HTTP proxy, timeout, and logging settings.
/// </summary>
/// <param name="proxy">The proxy to use for HTTP connections. Defaults to null.</param>
/// <param name="timeout">The optional timeout to use for HTTP requests. Set to <see cref="System.Threading.Timeout.InfiniteTimeSpan"/> to disable timeouts. Defaults to null.</param>
/// <param name="useRelativeRateLimit">Whether to use the system clock for computing rate limit resets. See <see cref="DiscordConfiguration.UseRelativeRatelimit"/> for more details. Defaults to true.</param>
/// <param name="loggerFactory">The optional logging factory to use for this client. Defaults to null.</param>
/// <param name="minimumLogLevel">The minimum logging level for messages. Defaults to information.</param>
/// <param name="logTimestampFormat">The timestamp format to use for the logger.</param>
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;
if (loggerFactory == null)
{
loggerFactory = new DefaultLoggerFactory();
loggerFactory.AddProvider(new DefaultLoggerProvider(this));
}
this.Logger = loggerFactory.CreateLogger<DiscordWebhookClient>();
var parsedTimeout = timeout ?? TimeSpan.FromSeconds(10);
this.Apiclient = new DiscordApiClient(proxy, parsedTimeout, useRelativeRateLimit, this.Logger);
this.Hooks = new List<DiscordWebhook>();
this.Webhooks = new ReadOnlyCollection<DiscordWebhook>(this.Hooks);
}
/// <summary>
/// Registers a webhook with this client. This retrieves a webhook based on the ID and token supplied.
/// </summary>
/// <param name="id">The ID of the webhook to add.</param>
/// <param name="token">The token of the webhook to add.</param>
/// <returns>The registered webhook.</returns>
public async Task<DiscordWebhook> 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))
throw new InvalidOperationException("This webhook is registered with this client.");
var wh = await this.Apiclient.GetWebhookWithTokenAsync(id, token).ConfigureAwait(false);
this.Hooks.Add(wh);
return wh;
}
/// <summary>
/// Registers a webhook with this client. This retrieves a webhook from webhook URL.
/// </summary>
/// <param name="url">URL of the webhook to retrieve. This URL must contain both ID and token.</param>
/// <returns>The registered webhook.</returns>
public Task<DiscordWebhook> AddWebhookAsync(Uri url)
{
if (url == null)
throw new ArgumentNullException(nameof(url));
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);
}
/// <summary>
/// Registers a webhook with this client. This retrieves a webhook using the supplied full discord client.
/// </summary>
/// <param name="id">ID of the webhook to register.</param>
/// <param name="client">Discord client to which the webhook will belong.</param>
/// <returns>The registered webhook.</returns>
public async Task<DiscordWebhook> AddWebhookAsync(ulong id, BaseDiscordClient client)
{
if (client == null)
throw new ArgumentNullException(nameof(client));
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);
this.Hooks.Add(wh);
return wh;
}
/// <summary>
/// Registers a webhook with this client. This reuses the supplied webhook object.
/// </summary>
/// <param name="webhook">Webhook to register.</param>
/// <returns>The registered webhook.</returns>
public DiscordWebhook AddWebhook(DiscordWebhook webhook)
{
if (webhook == null)
throw new ArgumentNullException(nameof(webhook));
if (this.Hooks.Any(x => x.Id == webhook.Id))
throw new ArgumentException("This webhook is already registered with this client.");
webhook.ApiClient = this.Apiclient;
this.Hooks.Add(webhook);
return webhook;
}
/// <summary>
/// Unregisters a webhook with this client.
/// </summary>
/// <param name="id">ID of the webhook to unregister.</param>
/// <returns>The unregistered webhook.</returns>
public DiscordWebhook RemoveWebhook(ulong 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);
return wh;
}
/// <summary>
/// Gets a registered webhook with specified ID.
/// </summary>
/// <param name="id">ID of the registered webhook to retrieve.</param>
/// <returns>The requested webhook.</returns>
public DiscordWebhook GetRegisteredWebhook(ulong id)
=> this.Hooks.FirstOrDefault(xw => xw.Id == id);
/// <summary>
/// Broadcasts a message to all registered webhooks.
/// </summary>
/// <param name="builder">Webhook builder filled with data to send.</param>
/// <returns>A dictionary of <see cref="DisCatSharp.Entities.DiscordWebhook"/>s and <see cref="DisCatSharp.Entities.DiscordMessage"/>s.</returns>
public async Task<Dictionary<DiscordWebhook, DiscordMessage>> BroadcastMessageAsync(DiscordWebhookBuilder builder)
{
var deadhooks = new List<DiscordWebhook>();
var messages = new Dictionary<DiscordWebhook, DiscordMessage>();
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);
return messages;
}
~DiscordWebhookClient()
{
this.Hooks.Clear();
this.Hooks = null;
this.Apiclient.Rest.Dispose();
}
}
diff --git a/DisCatSharp/DiscordConfiguration.cs b/DisCatSharp/DiscordConfiguration.cs
index c58c67d2d..ae5f091f4 100644
--- a/DisCatSharp/DiscordConfiguration.cs
+++ b/DisCatSharp/DiscordConfiguration.cs
@@ -1,291 +1,291 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Entities;
using DisCatSharp.Enums;
using DisCatSharp.Net.Udp;
using DisCatSharp.Net.WebSocket;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace DisCatSharp;
/// <summary>
/// Represents configuration for <see cref="DiscordClient"/> and <see cref="DiscordShardedClient"/>.
/// </summary>
public sealed class DiscordConfiguration
{
/// <summary>
/// Sets the token used to identify the client.
/// </summary>
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 = "";
/// <summary>
/// <para>Sets the type of the token used to identify the client.</para>
/// <para>Defaults to <see cref="TokenType.Bot"/>.</para>
/// </summary>
public TokenType TokenType { internal get; set; } = TokenType.Bot;
/// <summary>
/// <para>Sets the minimum logging level for messages.</para>
/// <para>Typically, the default value of <see cref="LogLevel.Information"/> is ok for most uses.</para>
/// </summary>
public LogLevel MinimumLogLevel { internal get; set; } = LogLevel.Information;
/// <summary>
/// Overwrites the api version.
/// Defaults to 10.
/// </summary>
public string ApiVersion { internal get; set; } = "10";
/// <summary>
/// <para>Sets whether to rely on Discord for NTP (Network Time Protocol) synchronization with the "X-Ratelimit-Reset-After" header.</para>
/// <para>If the system clock is unsynced, setting this to true will ensure ratelimits are synced with Discord and reduce the risk of hitting one.</para>
/// <para>This should only be set to false if the system clock is synced with NTP.</para>
/// <para>Defaults to true.</para>
/// </summary>
public bool UseRelativeRatelimit { internal get; set; } = true;
/// <summary>
/// <para>Allows you to overwrite the time format used by the internal debug logger.</para>
/// <para>Only applicable when <see cref="LoggerFactory"/> is set left at default value. Defaults to ISO 8601-like format.</para>
/// </summary>
public string LogTimestampFormat { internal get; set; } = "yyyy-MM-dd HH:mm:ss zzz";
/// <summary>
/// <para>Sets the member count threshold at which guilds are considered large.</para>
/// <para>Defaults to 250.</para>
/// </summary>
public int LargeThreshold { internal get; set; } = 250;
/// <summary>
/// <para>Sets whether to automatically reconnect in case a connection is lost.</para>
/// <para>Defaults to true.</para>
/// </summary>
public bool AutoReconnect { internal get; set; } = true;
/// <summary>
/// <para>Sets the ID of the shard to connect to.</para>
/// <para>If not sharding, or sharding automatically, this value should be left with the default value of 0.</para>
/// </summary>
public int ShardId { internal get; set; }
/// <summary>
/// <para>Sets the total number of shards the bot is on. If not sharding, this value should be left with a default value of 1.</para>
/// <para>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.</para>
/// </summary>
public int ShardCount { internal get; set; } = 1;
/// <summary>
/// <para>Sets the level of compression for WebSocket traffic.</para>
/// <para>Disabling this option will increase the amount of traffic sent via WebSocket. Setting <see cref="GatewayCompressionLevel.Payload"/> will enable compression for READY and GUILD_CREATE payloads. Setting <see cref="GatewayCompressionLevel.Stream"/> will enable compression for the entire WebSocket stream, drastically reducing amount of traffic.</para>
/// <para>Defaults to <see cref="GatewayCompressionLevel.Stream"/>.</para>
/// </summary>
public GatewayCompressionLevel GatewayCompressionLevel { internal get; set; } = GatewayCompressionLevel.Stream;
/// <summary>
/// <para>Sets the size of the global message cache.</para>
/// <para>Setting this to 0 will disable message caching entirely. Defaults to 1024.</para>
/// </summary>
public int MessageCacheSize { internal get; set; } = 1024;
/// <summary>
/// <para>Sets the proxy to use for HTTP and WebSocket connections to Discord.</para>
/// <para>Defaults to null.</para>
/// </summary>
public IWebProxy Proxy { internal get; set; }
/// <summary>
/// <para>Sets the timeout for HTTP requests.</para>
/// <para>Set to <see cref="System.Threading.Timeout.InfiniteTimeSpan"/> to disable timeouts.</para>
/// <para>Defaults to 20 seconds.</para>
/// </summary>
public TimeSpan HttpTimeout { internal get; set; } = TimeSpan.FromSeconds(20);
/// <summary>
/// <para>Defines that the client should attempt to reconnect indefinitely.</para>
/// <para>This is typically a very bad idea to set to <c>true</c>, as it will swallow all connection errors.</para>
/// <para>Defaults to false.</para>
/// </summary>
public bool ReconnectIndefinitely { internal get; set; }
/// <summary>
/// Sets whether the client should attempt to cache members if exclusively using unprivileged intents.
/// <para>
/// This will only take effect if there are no <see cref="DiscordIntents.GuildMembers"/> or <see cref="DiscordIntents.GuildPresences"/>
/// intents specified. Otherwise, this will always be overwritten to true.
/// </para>
/// <para>Defaults to true.</para>
/// </summary>
public bool AlwaysCacheMembers { internal get; set; } = true;
/// <summary>
/// <para>Sets the gateway intents for this client.</para>
/// <para>If set, the client will only receive events that they specify with intents.</para>
/// <para>Defaults to <see cref="DiscordIntents.AllUnprivileged"/>.</para>
/// </summary>
public DiscordIntents Intents { internal get; set; } = DiscordIntents.AllUnprivileged;
/// <summary>
/// <para>Sets the factory method used to create instances of WebSocket clients.</para>
/// <para>Use <see cref="WebSocketClient.CreateNew"/> and equivalents on other implementations to switch out client implementations.</para>
/// <para>Defaults to <see cref="WebSocketClient.CreateNew"/>.</para>
/// </summary>
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;
/// <summary>
/// <para>Sets the factory method used to create instances of UDP clients.</para>
/// <para>Use <see cref="DcsUdpClient.CreateNew"/> and equivalents on other implementations to switch out client implementations.</para>
/// <para>Defaults to <see cref="DcsUdpClient.CreateNew"/>.</para>
/// </summary>
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;
/// <summary>
/// <para>Sets the logger implementation to use.</para>
/// <para>To create your own logger, implement the <see cref="Microsoft.Extensions.Logging.ILoggerFactory"/> instance.</para>
/// <para>Defaults to built-in implementation.</para>
/// </summary>
public ILoggerFactory LoggerFactory { internal get; set; }
/// <summary>
/// <para>Sets if the bot's status should show the mobile icon.</para>
/// <para>Defaults to false.</para>
/// </summary>
public bool MobileStatus { internal get; set; }
/// <summary>
/// <para>Whether to use canary. <see cref="UsePtb"/> has to be false.</para>
/// <para>Defaults to false.</para>
/// </summary>
public bool UseCanary { internal get; set; }
/// <summary>
/// <para>Whether to use ptb. <see cref="UseCanary"/> has to be false.</para>
/// <para>Defaults to false.</para>
/// </summary>
public bool UsePtb { internal get; set; }
/// <summary>
/// <para>Refresh full guild channel cache.</para>
/// <para>Defaults to false.</para>
/// </summary>
public bool AutoRefreshChannelCache { internal get; set; }
/// <summary>
/// <para>Do not use, this is meant for DisCatSharp Devs.</para>
/// <para>Defaults to null.</para>
/// </summary>
public string Override { internal get; set; }
/// <summary>
/// Sets your preferred API language. See <see cref="DiscordLocales" /> for valid locales.
/// </summary>
public string Locale { internal get; set; } = DiscordLocales.AMERICAN_ENGLISH;
/// <summary>
/// <para>Sets the service provider.</para>
/// <para>This allows passing data around without resorting to static members.</para>
/// <para>Defaults to an empty service provider.</para>
/// </summary>
public IServiceProvider ServiceProvider { internal get; set; } = new ServiceCollection().BuildServiceProvider(true);
/// <summary>
/// Creates a new configuration with default values.
/// </summary>
public DiscordConfiguration()
{ }
/// <summary>
/// Utilized via Dependency Injection Pipeline
/// </summary>
/// <param name="provider"></param>
[ActivatorUtilitiesConstructor]
public DiscordConfiguration(IServiceProvider provider)
{
this.ServiceProvider = provider;
}
/// <summary>
/// Creates a clone of another discord configuration.
/// </summary>
/// <param name="other">Client configuration to clone.</param>
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.UsePtb = other.UsePtb;
this.AutoRefreshChannelCache = other.AutoRefreshChannelCache;
this.ApiVersion = other.ApiVersion;
this.ServiceProvider = other.ServiceProvider;
this.Override = other.Override;
this.Locale = other.Locale;
}
}
diff --git a/DisCatSharp/Entities/Application/DiscordApplication.cs b/DisCatSharp/Entities/Application/DiscordApplication.cs
index baa176983..524b25834 100644
--- a/DisCatSharp/Entities/Application/DiscordApplication.cs
+++ b/DisCatSharp/Entities/Application/DiscordApplication.cs
@@ -1,440 +1,440 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents an OAuth2 application.
/// </summary>
public sealed class DiscordApplication : DiscordMessageApplication, IEquatable<DiscordApplication>
{
/// <summary>
/// Gets the application's summary.
/// </summary>
public string Summary { get; internal set; }
/// <summary>
/// Gets the application's icon.
/// </summary>
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;
/// <summary>
/// Gets the application's icon hash.
/// </summary>
public string IconHash { get; internal set; }
/// <summary>
/// Gets the application's allowed RPC origins.
/// </summary>
public IReadOnlyList<string> RpcOrigins { get; internal set; }
/// <summary>
/// Gets the application's flags.
/// </summary>
public ApplicationFlags Flags { get; internal set; }
/// <summary>
/// Gets the application's owners.
/// </summary>
public List<DiscordUser> Owners { get; internal set; }
/// <summary>
/// Gets whether this application's bot user requires code grant.
/// </summary>
public bool? RequiresCodeGrant { get; internal set; }
/// <summary>
/// Gets whether this bot application is public.
/// </summary>
public bool? IsPublic { get; internal set; }
/// <summary>
/// Gets the terms of service url of the application.
/// </summary>
public string TermsOfServiceUrl { get; internal set; }
/// <summary>
/// Gets the privacy policy url of the application.
/// </summary>
public string PrivacyPolicyUrl { get; internal set; }
/// <summary>
/// Gets the team name of the application.
/// </summary>
public string TeamName { get; internal set; }
/// <summary>
/// Gets the hash of the application's cover image.
/// </summary>
public string CoverImageHash { get; internal set; }
/// <summary>
/// Gets this application's cover image URL.
/// </summary>
public override string CoverImageUrl
=> $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.APP_ICONS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.CoverImageHash}.png?size=1024";
/// <summary>
/// Gets the team which owns this application.
/// </summary>
public DiscordTeam Team { get; internal set; }
/// <summary>
/// Gets the hex encoded key for verification in interactions and the GameSDK's GetTicket
/// </summary>
public string VerifyKey { get; internal set; }
/// <summary>
/// If this application is a game sold on Discord, this field will be the guild to which it has been linked
/// </summary>
public ulong? GuildId { get; internal set; }
/// <summary>
/// If this application is a game sold on Discord, this field will be the id of the "Game SKU" that is created, if exists
/// </summary>
public ulong? PrimarySkuId { get; internal set; }
/// <summary>
/// If this application is a game sold on Discord, this field will be the URL slug that links to the store page
/// </summary>
public string Slug { get; internal set; }
/// <summary>
/// Gets or sets a list of <see cref="DiscordApplicationAsset"/>.
/// </summary>
private IReadOnlyList<DiscordApplicationAsset> _assets;
/// <summary>
/// A custom url for the Add To Server button.
/// </summary>
public string CustomInstallUrl { get; internal set; }
/// <summary>
/// Install parameters for adding the application to a guild.
/// </summary>
public DiscordApplicationInstallParams InstallParams { get; internal set; }
/// <summary>
/// The application's role connection verification entry point,
/// which when configured will render the app as a verification method in the guild role verification configuration.
/// </summary>
public string RoleConnectionsVerificationUrl { get; internal set; }
/// <summary>
/// The application tags.
/// Not used atm.
/// </summary>
public IReadOnlyList<string> Tags { get; internal set; }
/// <summary>
/// Whether the application is hooked.
/// </summary>
public bool IsHook { get; internal set; }
/// <summary>
/// Gets the application type.
/// Mostly null.
/// </summary>
public string Type { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordApplication"/> class.
/// </summary>
internal DiscordApplication()
{ }
/// <summary>
/// Gets the application's cover image URL, in requested format and size.
/// </summary>
/// <param name="fmt">Format of the image to get.</param>
/// <param name="size">Maximum size of the cover image. Must be a power of two, minimum 16, maximum 2048.</param>
/// <returns>URL of the application's cover image.</returns>
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;
}
/// <summary>
/// Retrieves this application's assets.
/// </summary>
/// <returns>This application's assets.</returns>
public async Task<IReadOnlyList<DiscordApplicationAsset>> GetAssetsAsync()
{
this._assets ??= await this.Discord.ApiClient.GetApplicationAssetsAsync(this).ConfigureAwait(false);
return this._assets;
}
/// <summary>
/// Generates an oauth url for the application.
/// </summary>
/// <param name="permissions">The permissions.</param>
/// <returns>OAuth Url</returns>
public string GenerateBotOAuth(Permissions permissions = Permissions.None)
{
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();
}
/// <summary>
/// Checks whether this <see cref="DiscordApplication"/> is equal to another object.
/// </summary>
/// <param name="obj">Object to compare to.</param>
/// <returns>Whether the object is equal to this <see cref="DiscordApplication"/>.</returns>
public override bool Equals(object obj)
=> this.Equals(obj as DiscordApplication);
/// <summary>
/// Checks whether this <see cref="DiscordApplication"/> is equal to another <see cref="DiscordApplication"/>.
/// </summary>
/// <param name="e"><see cref="DiscordApplication"/> to compare to.</param>
/// <returns>Whether the <see cref="DiscordApplication"/> is equal to this <see cref="DiscordApplication"/>.</returns>
public bool Equals(DiscordApplication e)
=> e is not null && (ReferenceEquals(this, e) || this.Id == e.Id);
/// <summary>
/// Gets the hash code for this <see cref="DiscordApplication"/>.
/// </summary>
/// <returns>The hash code for this <see cref="DiscordApplication"/>.</returns>
public override int GetHashCode()
=> this.Id.GetHashCode();
/// <summary>
/// Gets whether the two <see cref="DiscordApplication"/> objects are equal.
/// </summary>
/// <param name="e1">First application to compare.</param>
/// <param name="e2">Second application to compare.</param>
/// <returns>Whether the two applications are equal.</returns>
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);
}
/// <summary>
/// Gets whether the two <see cref="DiscordApplication"/> objects are not equal.
/// </summary>
/// <param name="e1">First application to compare.</param>
/// <param name="e2">Second application to compare.</param>
/// <returns>Whether the two applications are not equal.</returns>
public static bool operator !=(DiscordApplication e1, DiscordApplication e2)
=> !(e1 == e2);
}
/// <summary>
/// Represents an discord asset.
/// </summary>
public abstract class DiscordAsset
{
/// <summary>
/// Gets the ID of this asset.
/// </summary>
public virtual string Id { get; set; }
/// <summary>
/// Gets the URL of this asset.
/// </summary>
public abstract Uri Url { get; }
}
/// <summary>
/// Represents an asset for an OAuth2 application.
/// </summary>
public sealed class DiscordApplicationAsset : DiscordAsset, IEquatable<DiscordApplicationAsset>
{
/// <summary>
/// Gets the Discord client instance for this asset.
/// </summary>
internal BaseDiscordClient Discord { get; set; }
/// <summary>
/// Gets the asset's name.
/// </summary>
[JsonProperty("name")]
public string Name { get; internal set; }
/// <summary>
/// Gets the asset's type.
/// </summary>
[JsonProperty("type")]
public ApplicationAssetType Type { get; internal set; }
/// <summary>
/// Gets the application this asset belongs to.
/// </summary>
public DiscordApplication Application { get; internal set; }
/// <summary>
/// Gets the Url of this asset.
/// </summary>
public override Uri Url
=> new($"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.APP_ASSETS}/{this.Application.Id.ToString(CultureInfo.InvariantCulture)}/{this.Id}.png");
/// <summary>
/// Initializes a new instance of the <see cref="DiscordApplicationAsset"/> class.
/// </summary>
internal DiscordApplicationAsset()
{ }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordApplicationAsset"/> class.
/// </summary>
/// <param name="app">The app.</param>
internal DiscordApplicationAsset(DiscordApplication app)
{
this.Discord = app.Discord;
}
/// <summary>
/// Checks whether this <see cref="DiscordApplicationAsset"/> is equal to another object.
/// </summary>
/// <param name="obj">Object to compare to.</param>
/// <returns>Whether the object is equal to this <see cref="DiscordApplicationAsset"/>.</returns>
public override bool Equals(object obj)
=> this.Equals(obj as DiscordApplicationAsset);
/// <summary>
/// Checks whether this <see cref="DiscordApplicationAsset"/> is equal to another <see cref="DiscordApplicationAsset"/>.
/// </summary>
/// <param name="e"><see cref="DiscordApplicationAsset"/> to compare to.</param>
/// <returns>Whether the <see cref="DiscordApplicationAsset"/> is equal to this <see cref="DiscordApplicationAsset"/>.</returns>
public bool Equals(DiscordApplicationAsset e)
=> e is not null && (ReferenceEquals(this, e) || this.Id == e.Id);
/// <summary>
/// Gets the hash code for this <see cref="DiscordApplication"/>.
/// </summary>
/// <returns>The hash code for this <see cref="DiscordApplication"/>.</returns>
public override int GetHashCode()
=> this.Id.GetHashCode();
/// <summary>
/// Gets whether the two <see cref="DiscordApplicationAsset"/> objects are equal.
/// </summary>
/// <param name="e1">First application asset to compare.</param>
/// <param name="e2">Second application asset to compare.</param>
/// <returns>Whether the two application assets not equal.</returns>
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);
}
/// <summary>
/// Gets whether the two <see cref="DiscordApplicationAsset"/> objects are not equal.
/// </summary>
/// <param name="e1">First application asset to compare.</param>
/// <param name="e2">Second application asset to compare.</param>
/// <returns>Whether the two application assets are not equal.</returns>
public static bool operator !=(DiscordApplicationAsset e1, DiscordApplicationAsset e2)
=> !(e1 == e2);
}
/// <summary>
/// Represents an spotify asset.
/// </summary>
public sealed class DiscordSpotifyAsset : DiscordAsset
{
/// <summary>
/// Gets the URL of this asset.
/// </summary>
public override Uri Url
=> this._url.Value;
private readonly Lazy<Uri> _url;
/// <summary>
/// Initializes a new instance of the <see cref="DiscordSpotifyAsset"/> class.
/// </summary>
public DiscordSpotifyAsset()
{
this._url = new Lazy<Uri>(() =>
{
var ids = this.Id.Split(':');
var id = ids[1];
return new Uri($"https://i.scdn.co/image/{id}");
});
}
}
/// <summary>
/// Determines the type of the asset attached to the application.
/// </summary>
public enum ApplicationAssetType : int
{
/// <summary>
/// Unknown type. This indicates something went terribly wrong.
/// </summary>
Unknown = 0,
/// <summary>
/// This asset can be used as small image for rich presences.
/// </summary>
SmallImage = 1,
/// <summary>
/// This asset can be used as large image for rich presences.
/// </summary>
LargeImage = 2
}
diff --git a/DisCatSharp/Entities/Application/DiscordApplicationCommand.cs b/DisCatSharp/Entities/Application/DiscordApplicationCommand.cs
index dc6712f3c..61cf5a5be 100644
--- a/DisCatSharp/Entities/Application/DiscordApplicationCommand.cs
+++ b/DisCatSharp/Entities/Application/DiscordApplicationCommand.cs
@@ -1,224 +1,224 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a command that is registered to an application.
/// </summary>
public sealed class DiscordApplicationCommand : SnowflakeObject, IEquatable<DiscordApplicationCommand>
{
/// <summary>
/// Gets the type of this application command.
/// </summary>
[JsonProperty("type")]
public ApplicationCommandType Type { get; internal set; }
/// <summary>
/// Gets the unique ID of this command's application.
/// </summary>
[JsonProperty("application_id")]
public ulong ApplicationId { get; internal set; }
/// <summary>
/// Gets the name of this command.
/// </summary>
[JsonProperty("name")]
public string Name { get; internal set; }
/// <summary>
/// Sets the name localizations.
/// </summary>
[JsonProperty("name_localizations", NullValueHandling = NullValueHandling.Ignore)]
internal Dictionary<string, string> RawNameLocalizations { get; set; }
/// <summary>
/// Gets the name localizations.
/// </summary>
[JsonIgnore]
public DiscordApplicationCommandLocalization NameLocalizations
=> new(this.RawNameLocalizations);
/// <summary>
/// Gets the description of this command.
/// </summary>
[JsonProperty("description")]
public string Description { get; internal set; }
/// <summary>
/// Sets the description localizations.
/// </summary>
[JsonProperty("description_localizations", NullValueHandling = NullValueHandling.Ignore)]
internal Dictionary<string, string> RawDescriptionLocalizations { get; set; }
/// <summary>
/// Gets the description localizations.
/// </summary>
[JsonIgnore]
public DiscordApplicationCommandLocalization DescriptionLocalizations
=> new(this.RawDescriptionLocalizations);
/// <summary>
/// Gets the potential parameters for this command.
/// </summary>
[JsonProperty("options", NullValueHandling = NullValueHandling.Ignore)]
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public List<DiscordApplicationCommandOption>? Options { get; internal set; } = null;
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
/// <summary>
/// Gets the commands needed permissions.
/// </summary>
[JsonProperty("default_member_permissions", NullValueHandling = NullValueHandling.Ignore)]
public Permissions? DefaultMemberPermissions { get; internal set; } = null;
/// <summary>
/// Gets whether the command can be used in direct messages.
/// </summary>
[JsonProperty("dm_permission", NullValueHandling = NullValueHandling.Ignore)]
public bool? DmPermission { get; internal set; }
/// <summary>
/// Gets whether the command is marked as NSFW.
/// </summary>
[JsonProperty("nsfw", NullValueHandling = NullValueHandling.Ignore)]
public bool IsNsfw { get; internal set; } = false;
/// <summary>
/// Gets the version number for this command.
/// </summary>
[JsonProperty("version")]
public ulong Version { get; internal set; }
/// <summary>
/// Gets the name localizations.
/// </summary>
[JsonIgnore]
public string Mention => this.Type == ApplicationCommandType.ChatInput ? $"</{this.Name}:{this.Id}>" : this.Name;
/// <summary>
/// Creates a new instance of a <see cref="DiscordApplicationCommand"/>.
/// </summary>
/// <param name="name">The name of the command.</param>
/// <param name="description">The description of the command.</param>
/// <param name="options">Optional parameters for this command.</param>
/// <param name="type">The type of the command. Defaults to ChatInput.</param>
/// <param name="nameLocalizations">The localizations of the command name.</param>
/// <param name="descriptionLocalizations">The localizations of the command description.</param>
/// <param name="defaultMemberPermissions">The default member permissions.</param>
/// <param name="dmPermission">The dm permission.</param>
/// <param name="isNsfw">Whether this command is NSFW.</param>
public DiscordApplicationCommand(
string name, string description,
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
IEnumerable<DiscordApplicationCommandOption>? options = null,
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
ApplicationCommandType type = ApplicationCommandType.ChatInput,
DiscordApplicationCommandLocalization nameLocalizations = null, DiscordApplicationCommandLocalization descriptionLocalizations = null,
Permissions? defaultMemberPermissions = null, bool? dmPermission = null, bool isNsfw = false)
{
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));
if (string.IsNullOrWhiteSpace(description))
throw new ArgumentException("Slash commands need a description.", 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 && options.Any() ? options.ToList() : null;
this.Type = type;
this.Name = name;
this.Description = description;
this.Options = optionsList;
this.DefaultMemberPermissions = defaultMemberPermissions;
this.DmPermission = dmPermission;
this.IsNsfw = isNsfw;
}
/// <summary>
/// Checks whether this <see cref="DiscordApplicationCommand"/> object is equal to another object.
/// </summary>
/// <param name="other">The command to compare to.</param>
/// <returns>Whether the command is equal to this <see cref="DiscordApplicationCommand"/>.</returns>
public bool Equals(DiscordApplicationCommand other)
=> this.Id == other.Id;
/// <summary>
/// Determines if two <see cref="DiscordApplicationCommand"/> objects are equal.
/// </summary>
/// <param name="e1">The first command object.</param>
/// <param name="e2">The second command object.</param>
/// <returns>Whether the two <see cref="DiscordApplicationCommand"/> objects are equal.</returns>
public static bool operator ==(DiscordApplicationCommand e1, DiscordApplicationCommand e2)
=> e1.Equals(e2);
/// <summary>
/// Determines if two <see cref="DiscordApplicationCommand"/> objects are not equal.
/// </summary>
/// <param name="e1">The first command object.</param>
/// <param name="e2">The second command object.</param>
/// <returns>Whether the two <see cref="DiscordApplicationCommand"/> objects are not equal.</returns>
public static bool operator !=(DiscordApplicationCommand e1, DiscordApplicationCommand e2)
=> !(e1 == e2);
/// <summary>
/// Determines if a <see cref="object"/> is equal to the current <see cref="DiscordApplicationCommand"/>.
/// </summary>
/// <param name="other">The object to compare to.</param>
/// <returns>Whether the two <see cref="DiscordApplicationCommand"/> objects are not equal.</returns>
public override bool Equals(object other)
=> other is DiscordApplicationCommand dac && this.Equals(dac);
/// <summary>
/// Gets the hash code for this <see cref="DiscordApplicationCommand"/>.
/// </summary>
/// <returns>The hash code for this <see cref="DiscordApplicationCommand"/>.</returns>
public override int GetHashCode()
=> this.Id.GetHashCode();
}
diff --git a/DisCatSharp/Entities/Application/DiscordApplicationCommandAutocompleteChoice.cs b/DisCatSharp/Entities/Application/DiscordApplicationCommandAutocompleteChoice.cs
index f68241355..d9566e49a 100644
--- a/DisCatSharp/Entities/Application/DiscordApplicationCommandAutocompleteChoice.cs
+++ b/DisCatSharp/Entities/Application/DiscordApplicationCommandAutocompleteChoice.cs
@@ -1,79 +1,79 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents an option for a user to select for auto-completion.
/// </summary>
public sealed class DiscordApplicationCommandAutocompleteChoice
{
/// <summary>
/// Gets the name of this option which will be presented to the user.
/// </summary>
[JsonProperty("name")]
public string Name { get; internal set; }
/// <summary>
/// Sets the name localizations.
/// </summary>
[JsonProperty("name_localizations", NullValueHandling = NullValueHandling.Ignore)]
internal Dictionary<string, string> RawNameLocalizations { get; set; }
/// <summary>
/// Gets the name localizations.
/// </summary>
[JsonIgnore]
public DiscordApplicationCommandLocalization NameLocalizations
=> new(this.RawNameLocalizations);
/// <summary>
/// Gets the value of this option.
/// </summary>
[JsonProperty("value")]
public object Value { get; internal set; }
/// <summary>
/// Creates a new instance of <see cref="DiscordApplicationCommandAutocompleteChoice"/>.
/// </summary>
/// <param name="name">The name of this option, which will be presented to the user.</param>
/// <param name="nameLocalizations">The localizations of the option name.</param>
/// <param name="value">The value of this option.</param>
public DiscordApplicationCommandAutocompleteChoice(string name, object value, DiscordApplicationCommandLocalization nameLocalizations = null)
{
if (name.Length > 100)
throw new ArgumentException("Application command choice name cannot exceed 100 characters.", nameof(name));
if (value is string val && val.Length > 100)
throw new ArgumentException("Application command choice value cannot exceed 100 characters.", nameof(value));
if (!(value is string || value is long || value is int || value is double))
throw new InvalidOperationException($"Only {typeof(string)}, {typeof(long)}, {typeof(double)} or {typeof(int)} types may be passed to a autocomplete choice.");
this.Name = name;
this.RawNameLocalizations = nameLocalizations?.GetKeyValuePairs();
this.Value = value;
}
}
diff --git a/DisCatSharp/Entities/Application/DiscordApplicationCommandLocalization.cs b/DisCatSharp/Entities/Application/DiscordApplicationCommandLocalization.cs
index f713af913..eb7d0b982 100644
--- a/DisCatSharp/Entities/Application/DiscordApplicationCommandLocalization.cs
+++ b/DisCatSharp/Entities/Application/DiscordApplicationCommandLocalization.cs
@@ -1,105 +1,105 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a application command localization.
/// </summary>
public sealed class DiscordApplicationCommandLocalization
{
/// <summary>
/// Gets the localization dict.
/// </summary>
public Dictionary<string, string> Localizations { get; internal set; }
/// <summary>
/// Gets valid [locales](xref:modules_application_commands_translations_reference#valid-locales) for Discord.
/// </summary>
internal List<string> 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" };
/// <summary>
/// Adds a localization.
/// </summary>
/// <param name="locale">The [locale](xref:modules_application_commands_translations_reference#valid-locales) to add.</param>
/// <param name="value">The translation to add.</param>
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)}");
}
}
/// <summary>
/// Removes a localization.
/// </summary>
/// <param name="locale">The [locale](xref:modules_application_commands_translations_reference#valid-locales) to remove.</param>
public void RemoveLocalization(string locale)
=> this.Localizations.Remove(locale);
/// <summary>
/// Initializes a new instance of <see cref="DiscordApplicationCommandLocalization"/>.
/// </summary>
public DiscordApplicationCommandLocalization() { }
/// <summary>
/// Initializes a new instance of <see cref="DiscordApplicationCommandLocalization"/>.
/// </summary>
/// <param name="localizations">Localizations.</param>
public DiscordApplicationCommandLocalization(Dictionary<string, string> 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)}");
}
}
this.Localizations = localizations;
}
/// <summary>
/// Gets the KVPs.
/// </summary>
/// <returns></returns>
public Dictionary<string, string> GetKeyValuePairs()
=> this.Localizations;
/// <summary>
/// Whether the [locale](xref:modules_application_commands_translations_reference#valid-locales) to be added is valid for Discord.
/// </summary>
/// <param name="lang">[Locale](xref:modules_application_commands_translations_reference#valid-locales) string.</param>
public bool Validate(string lang)
=> this.ValidLocales.Contains(lang);
}
diff --git a/DisCatSharp/Entities/Application/DiscordApplicationCommandOption.cs b/DisCatSharp/Entities/Application/DiscordApplicationCommandOption.cs
index 4985f0dbf..995b19c3e 100644
--- a/DisCatSharp/Entities/Application/DiscordApplicationCommandOption.cs
+++ b/DisCatSharp/Entities/Application/DiscordApplicationCommandOption.cs
@@ -1,195 +1,195 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a parameter for a <see cref="DiscordApplicationCommand"/>.
/// </summary>
public sealed class DiscordApplicationCommandOption
{
/// <summary>
/// Gets the type of this command parameter.
/// </summary>
[JsonProperty("type")]
public ApplicationCommandOptionType Type { get; internal set; }
/// <summary>
/// Gets the name of this command parameter.
/// </summary>
[JsonProperty("name")]
public string Name { get; internal set; }
/// <summary>
/// Sets the name localizations.
/// </summary>
[JsonProperty("name_localizations", NullValueHandling = NullValueHandling.Ignore)]
internal Dictionary<string, string> RawNameLocalizations { get; set; }
/// <summary>
/// Gets the name localizations.
/// </summary>
[JsonIgnore]
public DiscordApplicationCommandLocalization NameLocalizations
=> new(this.RawNameLocalizations);
/// <summary>
/// Gets the description of this command parameter.
/// </summary>
[JsonProperty("description")]
public string Description { get; internal set; }
/// <summary>
/// Sets the description localizations.
/// </summary>
[JsonProperty("description_localizations", NullValueHandling = NullValueHandling.Ignore)]
internal Dictionary<string, string> RawDescriptionLocalizations { get; set; }
/// <summary>
/// Gets the description localizations.
/// </summary>
[JsonIgnore]
public DiscordApplicationCommandLocalization DescriptionLocalizations
=> new(this.RawDescriptionLocalizations);
/// <summary>
/// Gets whether this command parameter is required.
/// </summary>
[JsonProperty("required", NullValueHandling = NullValueHandling.Ignore)]
public bool Required { get; internal set; } = false;
/// <summary>
/// Gets the optional choices for this command parameter.
/// Not applicable for auto-complete options.
/// </summary>
[JsonProperty("choices", NullValueHandling = NullValueHandling.Ignore)]
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public List<DiscordApplicationCommandOptionChoice>? Choices { get; internal set; } = null;
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
/// <summary>
/// Gets the optional subcommand parameters for this parameter.
/// </summary>
[JsonProperty("options", NullValueHandling = NullValueHandling.Ignore)]
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public List<DiscordApplicationCommandOption>? Options { get; internal set; } = null;
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
/// <summary>
/// Gets the optional allowed channel types.
/// </summary>
[JsonProperty("channel_types", NullValueHandling = NullValueHandling.Ignore)]
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public List<ChannelType>? ChannelTypes { get; internal set; } = null;
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
/// <summary>
/// Gets whether this option provides autocompletion.
/// </summary>
[JsonProperty("autocomplete", NullValueHandling = NullValueHandling.Ignore)]
public bool AutoComplete { get; internal set; } = false;
/// <summary>
/// Gets the minimum value for this slash command parameter.
/// </summary>
[JsonProperty("min_value", NullValueHandling = NullValueHandling.Ignore)]
public object MinimumValue { get; internal set; }
/// <summary>
/// Gets the maximum value for this slash command parameter.
/// </summary>
[JsonProperty("max_value", NullValueHandling = NullValueHandling.Ignore)]
public object MaximumValue { get; internal set; }
/// <summary>
/// Gets the maximum length for this slash command parameter.
/// </summary>
[JsonProperty("min_length", NullValueHandling = NullValueHandling.Ignore)]
public int? MinimumLength { get; internal set; }
/// <summary>
/// Gets the minimum length for this slash command parameter.
/// </summary>
[JsonProperty("max_length", NullValueHandling = NullValueHandling.Ignore)]
public int? MaximumLength { get; internal set; }
/// <summary>
/// Creates a new instance of a <see cref="DiscordApplicationCommandOption"/>.
/// </summary>
/// <param name="name">The name of this parameter.</param>
/// <param name="description">The description of the parameter.</param>
/// <param name="type">The type of this parameter.</param>
/// <param name="required">Whether the parameter is required.</param>
/// <param name="choices">The optional choice selection for this parameter.</param>
/// <param name="options">The optional subcommands for this parameter.</param>
/// <param name="channelTypes">If the option is a channel type, the channels shown will be restricted to these types.</param>
/// <param name="autocomplete">Whether this option provides autocompletion.</param>
/// <param name="minimumValue">The minimum value for this parameter. Only valid for types <see cref="ApplicationCommandOptionType.Integer"/> or <see cref="ApplicationCommandOptionType.Number"/>.</param>
/// <param name="maximumValue">The maximum value for this parameter. Only valid for types <see cref="ApplicationCommandOptionType.Integer"/> or <see cref="ApplicationCommandOptionType.Number"/>.</param>
/// <param name="nameLocalizations">The localizations of the parameter name.</param>
/// <param name="descriptionLocalizations">The localizations of the parameter description.</param>
/// <param name="minimumLength">The minimum allowed length of the string. (Min 0)</param>
/// <param name="maximumLength">The maximum allowed length of the string. (Min 1)</param>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public DiscordApplicationCommandOption(string name, string description, ApplicationCommandOptionType type, bool required = false, IEnumerable<DiscordApplicationCommandOptionChoice>? choices = null, IEnumerable<DiscordApplicationCommandOption>? options = null, IEnumerable<ChannelType>? channelTypes = null, bool autocomplete = false, object minimumValue = null, object maximumValue = null, DiscordApplicationCommandLocalization nameLocalizations = null, DiscordApplicationCommandLocalization descriptionLocalizations = null, int? minimumLength = null, int? maximumLength = null)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
if (!Utilities.IsValidSlashCommandName(name))
throw new ArgumentException("Invalid application command option name specified. It must be below 32 characters and not contain any whitespace.", nameof(name));
if (name.Any(char.IsUpper))
throw new ArgumentException("Application command option name cannot have any upper case characters.", nameof(name));
if (description.Length > 100)
throw new ArgumentException("Application command option description cannot exceed 100 characters.", nameof(description));
if (autocomplete && (choices?.Any() ?? false))
throw new InvalidOperationException("Auto-complete slash command options cannot provide choices.");
if (type == ApplicationCommandOptionType.SubCommand || type == ApplicationCommandOptionType.SubCommandGroup)
if (string.IsNullOrWhiteSpace(description))
throw new ArgumentException("Slash commands need a description.", nameof(description));
this.Name = name;
this.Description = description;
this.Type = type;
this.Required = required;
this.Choices = choices != null && choices.Any() ? choices.ToList() : null;
this.Options = options != null && options.Any() ? options.ToList() : null;
this.ChannelTypes = channelTypes != null && channelTypes.Any() ? channelTypes.ToList() : null;
this.AutoComplete = autocomplete;
this.MinimumValue = minimumValue;
this.MaximumValue = maximumValue;
this.MinimumLength = minimumLength;
this.MaximumLength = maximumLength;
this.RawNameLocalizations = nameLocalizations?.GetKeyValuePairs();
this.RawDescriptionLocalizations = descriptionLocalizations?.GetKeyValuePairs();
}
}
diff --git a/DisCatSharp/Entities/Application/DiscordApplicationCommandOptionChoice.cs b/DisCatSharp/Entities/Application/DiscordApplicationCommandOptionChoice.cs
index a4634965a..edac6467e 100644
--- a/DisCatSharp/Entities/Application/DiscordApplicationCommandOptionChoice.cs
+++ b/DisCatSharp/Entities/Application/DiscordApplicationCommandOptionChoice.cs
@@ -1,80 +1,80 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a command parameter choice for a <see cref="DiscordApplicationCommandOption"/>.
/// </summary>
public sealed class DiscordApplicationCommandOptionChoice
{
/// <summary>
/// Gets the name of this choice parameter.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Sets the name localizations.
/// </summary>
[JsonProperty("name_localizations", NullValueHandling = NullValueHandling.Ignore)]
internal Dictionary<string, string> RawNameLocalizations { get; set; }
/// <summary>
/// Gets the name localizations.
/// </summary>
[JsonIgnore]
public DiscordApplicationCommandLocalization NameLocalizations
=> new(this.RawNameLocalizations);
/// <summary>
/// Gets the value of this choice parameter. This will either be a type of <see cref="int"/>, <see cref="long"/>, <see cref="double"/> or <see cref="string"/>.
/// </summary>
[JsonProperty("value")]
public object Value { get; set; }
/// <summary>
/// Creates a new instance of a <see cref="DiscordApplicationCommandOptionChoice"/>.
/// </summary>
/// <param name="name">The name of the parameter choice.</param>
/// <param name="value">The value of the parameter choice.</param>
/// <param name="nameLocalizations">The localizations of the parameter choice name.</param>
public DiscordApplicationCommandOptionChoice(string name, object value, DiscordApplicationCommandLocalization nameLocalizations = null)
{
if (!(value is string || value is long || value is int || value is double))
throw new InvalidOperationException($"Only {typeof(string)}, {typeof(long)}, {typeof(double)} or {typeof(int)} types may be passed to a command option choice.");
if (name.Length > 100)
throw new ArgumentException("Application command choice name cannot exceed 100 characters.", nameof(name));
if (value is string val && val.Length > 100)
throw new ArgumentException("Application command choice value cannot exceed 100 characters.", nameof(value));
this.Name = name;
this.RawNameLocalizations = nameLocalizations?.GetKeyValuePairs();
this.Value = value;
}
}
diff --git a/DisCatSharp/Entities/Application/DiscordApplicationCommandPermission.cs b/DisCatSharp/Entities/Application/DiscordApplicationCommandPermission.cs
index 746e3f1b0..aa58ca7b1 100644
--- a/DisCatSharp/Entities/Application/DiscordApplicationCommandPermission.cs
+++ b/DisCatSharp/Entities/Application/DiscordApplicationCommandPermission.cs
@@ -1,111 +1,111 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a application command permission.
/// </summary>
public sealed class DiscordApplicationCommandPermission : SnowflakeObject, IEquatable<DiscordApplicationCommandPermission>
{
/// <summary>
/// Gets the id of the role or user.
/// </summary>
[JsonProperty("id")]
public new ulong Id { get; set; }
/// <summary>
/// Gets the application command permission type.
/// </summary>
[JsonProperty("type")]
public ApplicationCommandPermissionType Type { get; set; }
/// <summary>
/// Gets the permission .
/// </summary>
[JsonProperty("permission")]
public bool Permission { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordApplicationCommandPermission"/> class.
/// </summary>
internal DiscordApplicationCommandPermission() { }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordApplicationCommandPermission"/> class.
/// </summary>
/// <param name="id">The Id of the role or user for this permission.</param>
/// <param name="type">Defines whether the permission effects a user or role.</param>
/// <param name="permission">The permission for this command. True allows the subject to use the command, false does not allow the subject to use the command.</param>
public DiscordApplicationCommandPermission(ulong id, ApplicationCommandPermissionType type, bool permission)
{
this.Id = id;
this.Type = type;
this.Permission = permission;
}
/// <summary>
/// Checks whether this <see cref="DiscordApplicationCommandPermission"/> object is equal to another object.
/// </summary>
/// <param name="other">The command to compare to.</param>
/// <returns>Whether the command is equal to this <see cref="DiscordApplicationCommandPermission"/>.</returns>
public bool Equals(DiscordApplicationCommandPermission other)
=> this.Id == other.Id;
/// <summary>
/// Determines if two <see cref="DiscordApplicationCommandPermission"/> objects are equal.
/// </summary>
/// <param name="e1">The first command object.</param>
/// <param name="e2">The second command object.</param>
/// <returns>Whether the two <see cref="DiscordApplicationCommandPermission"/> objects are equal.</returns>
public static bool operator ==(DiscordApplicationCommandPermission e1, DiscordApplicationCommandPermission e2)
=> e1.Equals(e2);
/// <summary>
/// Determines if two <see cref="DiscordApplicationCommandPermission"/> objects are not equal.
/// </summary>
/// <param name="e1">The first command object.</param>
/// <param name="e2">The second command object.</param>
/// <returns>Whether the two <see cref="DiscordApplicationCommandPermission"/> objects are not equal.</returns>
public static bool operator !=(DiscordApplicationCommandPermission e1, DiscordApplicationCommandPermission e2)
=> !(e1 == e2);
/// <summary>
/// Determines if a <see cref="object"/> is equal to the current <see cref="DiscordApplicationCommand"/>.
/// </summary>
/// <param name="other">The object to compare to.</param>
/// <returns>Whether the two <see cref="DiscordApplicationCommandPermission"/> objects are not equal.</returns>
public override bool Equals(object other) => other is DiscordApplicationCommandPermission dacp && this.Equals(dacp);
/// <summary>
/// Gets the hash code for this <see cref="DiscordApplicationCommandPermission"/>.
/// </summary>
/// <returns>The hash code for this <see cref="DiscordApplicationCommandPermission"/>.</returns>
public override int GetHashCode()
=> this.Id.GetHashCode();
}
diff --git a/DisCatSharp/Entities/Application/DiscordApplicationInstallParams.cs b/DisCatSharp/Entities/Application/DiscordApplicationInstallParams.cs
index e240a6158..d540d9bb8 100644
--- a/DisCatSharp/Entities/Application/DiscordApplicationInstallParams.cs
+++ b/DisCatSharp/Entities/Application/DiscordApplicationInstallParams.cs
@@ -1,52 +1,52 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// The application install params.
/// </summary>
public sealed class DiscordApplicationInstallParams
{
/// <summary>
/// Gets the scopes.
/// </summary>
[JsonProperty("scopes", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<string> Scopes { get; internal set; }
/// <summary>
/// Gets or sets the permissions.
/// </summary>
[JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)]
public Permissions? Permissions { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordApplicationInstallParams"/> class.
/// </summary>
internal DiscordApplicationInstallParams() { }
}
diff --git a/DisCatSharp/Entities/Application/DiscordApplicationRoleConnectionMetadata.cs b/DisCatSharp/Entities/Application/DiscordApplicationRoleConnectionMetadata.cs
index c33326703..8dfc570f9 100644
--- a/DisCatSharp/Entities/Application/DiscordApplicationRoleConnectionMetadata.cs
+++ b/DisCatSharp/Entities/Application/DiscordApplicationRoleConnectionMetadata.cs
@@ -1,144 +1,144 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using DisCatSharp.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a role connection metadata object that is registered to an application.
/// </summary>
public sealed class DiscordApplicationRoleConnectionMetadata : IEquatable<DiscordApplicationRoleConnectionMetadata>
{
/// <summary>
/// Gets the type of this role connection metadata object.
/// </summary>
[JsonProperty("type")]
public ApplicationRoleConnectionMetadataType Type { get; internal set; }
/// <summary>
/// The dictionary key for the metadata field.
/// Must be `a-z`, `0-9`, or `_` characters.
/// </summary>
[JsonProperty("key")]
public string Key { get; internal set; }
/// <summary>
/// Gets the name of the metadata field.
/// </summary>
[JsonProperty("name")]
public string Name { get; internal set; }
/// <summary>
/// Sets the name localizations.
/// </summary>
[JsonProperty("name_localizations", NullValueHandling = NullValueHandling.Ignore)]
internal Dictionary<string, string> RawNameLocalizations { get; set; }
/// <summary>
/// Gets the name localizations.
/// </summary>
[JsonIgnore]
public DiscordApplicationCommandLocalization NameLocalizations
=> new(this.RawNameLocalizations);
/// <summary>
/// Gets the description of the metadata field.
/// </summary>
[JsonProperty("description")]
public string Description { get; internal set; }
/// <summary>
/// Sets the description localizations.
/// </summary>
[JsonProperty("description_localizations", NullValueHandling = NullValueHandling.Ignore)]
internal Dictionary<string, string> RawDescriptionLocalizations { get; set; }
/// <summary>
/// Gets the description localizations.
/// </summary>
[JsonIgnore]
public DiscordApplicationCommandLocalization DescriptionLocalizations
=> new(this.RawDescriptionLocalizations);
/// <summary>
/// Creates a new instance of a <see cref="DiscordApplicationRoleConnectionMetadata"/>.
/// </summary>
public DiscordApplicationRoleConnectionMetadata(
ApplicationRoleConnectionMetadataType type, string key, string name, string description,
DiscordApplicationCommandLocalization nameLocalizations = null, DiscordApplicationCommandLocalization descriptionLocalizations = null
)
{
this.Type = type;
this.Key = key;
this.Name = name;
this.Description = description;
this.RawNameLocalizations = nameLocalizations?.GetKeyValuePairs();
this.RawDescriptionLocalizations = descriptionLocalizations?.GetKeyValuePairs();
}
/// <summary>
/// Checks whether this <see cref="DiscordApplicationRoleConnectionMetadata"/> object is equal to another object.
/// </summary>
/// <param name="other">The command to compare to.</param>
/// <returns>Whether the command is equal to this <see cref="DiscordApplicationRoleConnectionMetadata"/>.</returns>
public bool Equals(DiscordApplicationRoleConnectionMetadata other)
=> this.Key == other.Key;
/// <summary>
/// Determines if two <see cref="DiscordApplicationRoleConnectionMetadata"/> objects are equal.
/// </summary>
/// <param name="e1">The first command object.</param>
/// <param name="e2">The second command object.</param>
/// <returns>Whether the two <see cref="DiscordApplicationRoleConnectionMetadata"/> objects are equal.</returns>
public static bool operator ==(DiscordApplicationRoleConnectionMetadata e1, DiscordApplicationRoleConnectionMetadata e2)
=> e1.Equals(e2);
/// <summary>
/// Determines if two <see cref="DiscordApplicationRoleConnectionMetadata"/> objects are not equal.
/// </summary>
/// <param name="e1">The first command object.</param>
/// <param name="e2">The second command object.</param>
/// <returns>Whether the two <see cref="DiscordApplicationRoleConnectionMetadata"/> objects are not equal.</returns>
public static bool operator !=(DiscordApplicationRoleConnectionMetadata e1, DiscordApplicationRoleConnectionMetadata e2)
=> !(e1 == e2);
/// <summary>
/// Determines if a <see cref="object"/> is equal to the current <see cref="DiscordApplicationRoleConnectionMetadata"/>.
/// </summary>
/// <param name="other">The object to compare to.</param>
/// <returns>Whether the two <see cref="DiscordApplicationRoleConnectionMetadata"/> objects are not equal.</returns>
public override bool Equals(object other)
=> other is DiscordApplicationRoleConnectionMetadata dac && this.Equals(dac);
/// <summary>
/// Gets the hash code for this <see cref="DiscordApplicationRoleConnectionMetadata"/>.
/// </summary>
/// <returns>The hash code for this <see cref="DiscordApplicationRoleConnectionMetadata"/>.</returns>
public override int GetHashCode()
=> this.Key.GetHashCode();
}
diff --git a/DisCatSharp/Entities/Application/DiscordGuildApplicationCommandPermission.cs b/DisCatSharp/Entities/Application/DiscordGuildApplicationCommandPermission.cs
index 899bfecde..4be345b67 100644
--- a/DisCatSharp/Entities/Application/DiscordGuildApplicationCommandPermission.cs
+++ b/DisCatSharp/Entities/Application/DiscordGuildApplicationCommandPermission.cs
@@ -1,110 +1,110 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a guild application command permission.
/// </summary>
public sealed class DiscordGuildApplicationCommandPermission : SnowflakeObject, IEquatable<DiscordGuildApplicationCommandPermission>
{
/// <summary>
/// Gets the id of the command.
/// </summary>
[JsonProperty("id")]
public new ulong Id { get; set; }
/// <summary>
/// Gets the unique ID of this command's application.
/// </summary>
[JsonProperty("application_id")]
public ulong ApplicationId { get; set; }
/// <summary>
/// Gets the guild id this permission applies to.
/// </summary>
[JsonProperty("guild_id")]
public ulong GuildId { get; set; }
/// <summary>
/// Gets the guild this permission applies to.
/// </summary>
[JsonIgnore]
public DiscordGuild Guild
=> this.Discord.Guilds.TryGetValue(this.GuildId, out var guild) ? guild : null;
/// <summary>
/// Gets the permission array.
/// </summary>
[JsonProperty("permissions")]
public IReadOnlyList<DiscordApplicationCommandPermission> Permissions { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordGuildApplicationCommandPermission"/> class.
/// </summary>
internal DiscordGuildApplicationCommandPermission() { }
/// <summary>
/// Checks whether this <see cref="DiscordGuildApplicationCommandPermission"/> object is equal to another object.
/// </summary>
/// <param name="other">The command to compare to.</param>
/// <returns>Whether the command is equal to this <see cref="DiscordGuildApplicationCommandPermission"/>.</returns>
public bool Equals(DiscordGuildApplicationCommandPermission other)
=> this.Id == other.Id;
/// <summary>
/// Determines if two <see cref="DiscordGuildApplicationCommandPermission"/> objects are equal.
/// </summary>
/// <param name="e1">The first command object.</param>
/// <param name="e2">The second command object.</param>
/// <returns>Whether the two <see cref="DiscordGuildApplicationCommandPermission"/> objects are equal.</returns>
public static bool operator ==(DiscordGuildApplicationCommandPermission e1, DiscordGuildApplicationCommandPermission e2)
=> e1.Equals(e2);
/// <summary>
/// Determines if two <see cref="DiscordGuildApplicationCommandPermission"/> objects are not equal.
/// </summary>
/// <param name="e1">The first command object.</param>
/// <param name="e2">The second command object.</param>
/// <returns>Whether the two <see cref="DiscordGuildApplicationCommandPermission"/> objects are not equal.</returns>
public static bool operator !=(DiscordGuildApplicationCommandPermission e1, DiscordGuildApplicationCommandPermission e2)
=> !(e1 == e2);
/// <summary>
/// Determines if a <see cref="object"/> is equal to the current <see cref="DiscordApplicationCommand"/>.
/// </summary>
/// <param name="other">The object to compare to.</param>
/// <returns>Whether the two <see cref="DiscordGuildApplicationCommandPermission"/> objects are not equal.</returns>
public override bool Equals(object other) => other is DiscordGuildApplicationCommandPermission dgacp && this.Equals(dgacp);
/// <summary>
/// Gets the hash code for this <see cref="DiscordGuildApplicationCommandPermission"/>.
/// </summary>
/// <returns>The hash code for this <see cref="DiscordGuildApplicationCommandPermission"/>.</returns>
public override int GetHashCode()
=> this.Id.GetHashCode();
}
diff --git a/DisCatSharp/Entities/Application/DiscordRpcApplication.cs b/DisCatSharp/Entities/Application/DiscordRpcApplication.cs
index cf658b3ba..dea7fa23e 100644
--- a/DisCatSharp/Entities/Application/DiscordRpcApplication.cs
+++ b/DisCatSharp/Entities/Application/DiscordRpcApplication.cs
@@ -1,169 +1,169 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using DisCatSharp.Net;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents an OAuth2 application.
/// </summary>
public sealed class DiscordRpcApplication : SnowflakeObject, IEquatable<DiscordRpcApplication>
{
[JsonProperty("name")]
public string Name;
[JsonProperty("icon")]
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public string? IconHash;
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
[JsonIgnore]
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public string? Icon
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
=> this.IconHash != null ? $"https://cdn.discordapp.com{Endpoints.APP_ICONS}/{this.Id}/{this.IconHash}.png" : null;
[JsonProperty("description")]
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public string? Description;
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
[JsonProperty("summary")]
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public string? Summary;
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
[JsonProperty("type")]
public string Type;
[JsonProperty("hook")]
public bool Hook;
[JsonProperty("guild_id")]
public ulong? GuildId;
[JsonProperty("bot_public")]
public bool IsPublic;
[JsonProperty("bot_require_code_grant")]
public bool RequiresCodeGrant;
[JsonProperty("terms_of_service_url")]
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public string? TermsOfServiceUrl;
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
[JsonProperty("privacy_policy_url")]
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public string? PrivacyPolicyUrl;
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
[JsonProperty("install_params")]
public DiscordApplicationInstallParams InstallParams;
[JsonProperty("verify_key")]
public string VerifyKey;
[JsonProperty("flags")]
public ApplicationFlags Flags;
[JsonProperty("tags")]
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public List<string>? Tags;
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
/// <summary>
/// Initializes a new instance of the <see cref="DiscordRpcApplication"/> class.
/// </summary>
internal DiscordRpcApplication()
{ }
/// <summary>
/// Generates an oauth url for the application.
/// </summary>
/// <param name="permissions">The permissions.</param>
/// <returns>OAuth Url</returns>
public string GenerateBotOAuth(Permissions permissions = Permissions.None)
{
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();
}
/// <summary>
/// Checks whether this <see cref="DiscordRpcApplication"/> is equal to another object.
/// </summary>
/// <param name="obj">Object to compare to.</param>
/// <returns>Whether the object is equal to this <see cref="DiscordRpcApplication"/>.</returns>
public override bool Equals(object obj)
=> this.Equals(obj as DiscordRpcApplication);
/// <summary>
/// Checks whether this <see cref="DiscordRpcApplication"/> is equal to another <see cref="DiscordRpcApplication"/>.
/// </summary>
/// <param name="e"><see cref="DiscordRpcApplication"/> to compare to.</param>
/// <returns>Whether the <see cref="DiscordRpcApplication"/> is equal to this <see cref="DiscordRpcApplication"/>.</returns>
public bool Equals(DiscordRpcApplication e)
=> e is not null && (ReferenceEquals(this, e) || this.Id == e.Id);
/// <summary>
/// Gets the hash code for this <see cref="DiscordRpcApplication"/>.
/// </summary>
/// <returns>The hash code for this <see cref="DiscordRpcApplication"/>.</returns>
public override int GetHashCode()
=> this.Id.GetHashCode();
/// <summary>
/// Gets whether the two <see cref="DiscordRpcApplication"/> objects are equal.
/// </summary>
/// <param name="e1">First application to compare.</param>
/// <param name="e2">Second application to compare.</param>
/// <returns>Whether the two applications are equal.</returns>
public static bool operator ==(DiscordRpcApplication e1, DiscordRpcApplication 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);
}
/// <summary>
/// Gets whether the two <see cref="DiscordRpcApplication"/> objects are not equal.
/// </summary>
/// <param name="e1">First application to compare.</param>
/// <param name="e2">Second application to compare.</param>
/// <returns>Whether the two applications are not equal.</returns>
public static bool operator !=(DiscordRpcApplication e1, DiscordRpcApplication e2)
=> !(e1 == e2);
}
diff --git a/DisCatSharp/Entities/Channel/DiscordChannel.cs b/DisCatSharp/Entities/Channel/DiscordChannel.cs
index 8d71f1379..ddd496382 100644
--- a/DisCatSharp/Entities/Channel/DiscordChannel.cs
+++ b/DisCatSharp/Entities/Channel/DiscordChannel.cs
@@ -1,1478 +1,1478 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Attributes;
using DisCatSharp.Enums;
using DisCatSharp.Exceptions;
using DisCatSharp.Net.Abstractions;
using DisCatSharp.Net.Models;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a discord channel.
/// </summary>
public class DiscordChannel : SnowflakeObject, IEquatable<DiscordChannel>
{
/// <summary>
/// Gets ID of the guild to which this channel belongs.
/// </summary>
[JsonProperty("guild_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? GuildId { get; internal set; }
/// <summary>
/// Gets ID of the category that contains this channel.
/// </summary>
[JsonProperty("parent_id", NullValueHandling = NullValueHandling.Include)]
public ulong? ParentId { get; internal set; }
/// <summary>
/// Gets the category that contains this channel.
/// </summary>
[JsonIgnore]
public DiscordChannel Parent
=> this.ParentId.HasValue ? this.Guild.GetChannel(this.ParentId.Value) : null;
/// <summary>
/// Gets the name of this channel.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; internal set; }
/// <summary>
/// Gets the type of this channel.
/// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
public ChannelType Type { get; internal set; }
/// <summary>
/// Gets the template for new posts in this channel.
/// Applicable if forum channel.
/// </summary>
[JsonProperty("template", NullValueHandling = NullValueHandling.Ignore)]
public string Template { get; internal set; }
/// <summary>
/// Gets the position of this channel.
/// </summary>
[JsonProperty("position", NullValueHandling = NullValueHandling.Ignore)]
public int Position { get; internal set; }
/// <summary>
/// Gets the flags of this channel.
/// </summary>
[JsonProperty("flags", NullValueHandling = NullValueHandling.Ignore)]
public ChannelFlags Flags { get; internal set; }
/// <summary>
/// Gets the maximum available position to move the channel to.
/// This can contain outdated information.
/// </summary>
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).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).Last().Position
: channels.Where(xc => xc.ParentId == this.ParentId && xc.Type == this.Type).OrderBy(xc => xc.Position).Last().Position
: channels.Where(xc => xc.ParentId == null && xc.Type == this.Type).OrderBy(xc => xc.Position).Last().Position;
}
/// <summary>
/// Gets the minimum available position to move the channel to.
/// </summary>
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).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).First().Position
: channels.Where(xc => xc.ParentId == this.ParentId && xc.Type == this.Type).OrderBy(xc => xc.Position).First().Position
: channels.Where(xc => xc.ParentId == null && xc.Type == this.Type).OrderBy(xc => xc.Position).First().Position;
}
/// <summary>
/// Gets whether this channel is a DM channel.
/// </summary>
[JsonIgnore]
public bool IsPrivate
=> this.Type is ChannelType.Private or ChannelType.Group;
/// <summary>
/// Gets whether this channel is a channel category.
/// </summary>
[JsonIgnore]
public bool IsCategory
=> this.Type == ChannelType.Category;
/// <summary>
/// Gets whether this channel is a stage channel.
/// </summary>
[JsonIgnore]
public bool IsStage
=> this.Type == ChannelType.Stage;
/// <summary>
/// Gets the guild to which this channel belongs.
/// </summary>
[JsonIgnore]
public DiscordGuild Guild
=> this.GuildId.HasValue && this.Discord.Guilds.TryGetValue(this.GuildId.Value, out var guild) ? guild : null;
/// <summary>
/// Gets a collection of permission overwrites for this channel.
/// </summary>
[JsonIgnore]
public IReadOnlyList<DiscordOverwrite> PermissionOverwrites
=> this._permissionOverwritesLazy.Value;
[JsonProperty("permission_overwrites", NullValueHandling = NullValueHandling.Ignore)]
internal List<DiscordOverwrite> PermissionOverwritesInternal = new();
[JsonIgnore]
private readonly Lazy<IReadOnlyList<DiscordOverwrite>> _permissionOverwritesLazy;
/// <summary>
/// Gets the channel's topic. This is applicable to text channels only.
/// </summary>
[JsonProperty("topic", NullValueHandling = NullValueHandling.Ignore)]
public string Topic { get; internal set; }
/// <summary>
/// Gets the ID of the last message sent in this channel. This is applicable to text channels only.
/// </summary>
[JsonProperty("last_message_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? LastMessageId { get; internal set; }
/// <summary>
/// Gets this channel's bitrate. This is applicable to voice channels only.
/// </summary>
[JsonProperty("bitrate", NullValueHandling = NullValueHandling.Ignore)]
public int? Bitrate { get; internal set; }
/// <summary>
/// Gets this channel's user limit. This is applicable to voice channels only.
/// </summary>
[JsonProperty("user_limit", NullValueHandling = NullValueHandling.Ignore)]
public int? UserLimit { get; internal set; }
/// <summary>
/// <para>Gets the slow mode delay configured for this channel.</para>
/// <para>All bots, as well as users with <see cref="Permissions.ManageChannels"/> or <see cref="Permissions.ManageMessages"/> permissions in the channel are exempt from slow mode.</para>
/// </summary>
[JsonProperty("rate_limit_per_user", NullValueHandling = NullValueHandling.Ignore)]
public int? PerUserRateLimit { get; internal set; }
/// <summary>
/// <para>Gets the slow mode delay configured for this channel for post creations.</para>
/// <para>All bots, as well as users with <see cref="Permissions.ManageChannels"/> or <see cref="Permissions.ManageMessages"/> permissions in the channel are exempt from slow mode.</para>
/// </summary>
[JsonProperty("default_thread_rate_limit_per_user", NullValueHandling = NullValueHandling.Ignore)]
public int? PostCreateUserRateLimit { get; internal set; }
/// <summary>
/// Gets this channel's video quality mode. This is applicable to voice channels only.
/// </summary>
[JsonProperty("video_quality_mode", NullValueHandling = NullValueHandling.Ignore)]
public VideoQualityMode? QualityMode { get; internal set; }
/// <summary>
/// List of available tags for forum posts.
/// </summary>
[JsonIgnore]
public IReadOnlyList<ForumPostTag> AvailableTags => this.InternalAvailableTags;
/// <summary>
/// List of available tags for forum posts.
/// </summary>
[JsonProperty("available_tags", NullValueHandling = NullValueHandling.Ignore)]
internal List<ForumPostTag> InternalAvailableTags { get; set; } = new();
/// <summary>
/// List of available tags for forum posts.
/// </summary>
[JsonProperty("default_reaction_emoji", NullValueHandling = NullValueHandling.Ignore)]
public ForumReactionEmoji DefaultReactionEmoji { get; internal set; }
[JsonProperty("default_sort_order", NullValueHandling = NullValueHandling.Include)]
public ForumPostSortOrder? DefaultSortOrder { get; internal set; }
/// <summary>
/// Gets when the last pinned message was pinned.
/// </summary>
[JsonIgnore]
public DateTimeOffset? LastPinTimestamp
=> !string.IsNullOrWhiteSpace(this.LastPinTimestampRaw) && DateTimeOffset.TryParse(this.LastPinTimestampRaw, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dto) ?
dto : null;
/// <summary>
/// Gets when the last pinned message was pinned as raw string.
/// </summary>
[JsonProperty("last_pin_timestamp", NullValueHandling = NullValueHandling.Ignore)]
internal string LastPinTimestampRaw { get; set; }
/// <summary>
/// Gets this channel's default duration for newly created threads, in minutes, to automatically archive the thread after recent activity.
/// </summary>
[JsonProperty("default_auto_archive_duration", NullValueHandling = NullValueHandling.Ignore)]
public ThreadAutoArchiveDuration? DefaultAutoArchiveDuration { get; internal set; }
/// <summary>
/// Gets this channel's mention string.
/// </summary>
[JsonIgnore]
public string Mention
=> Formatter.Mention(this);
/// <summary>
/// Gets this channel's children. This applies only to channel categories.
/// </summary>
[JsonIgnore]
public IReadOnlyList<DiscordChannel> Children =>
!this.IsCategory
? throw new ArgumentException("Only channel categories contain children.")
: this.Guild.ChannelsInternal.Values.Where(e => e.ParentId == this.Id).ToList();
/// <summary>
/// Gets the list of members currently in the channel (if voice channel), or members who can see the channel (otherwise).
/// </summary>
[JsonIgnore]
public virtual IReadOnlyList<DiscordMember> Users =>
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();
/// <summary>
/// Gets whether this channel is an NSFW channel.
/// </summary>
[JsonProperty("nsfw")]
public bool IsNsfw { get; internal set; }
/// <summary>
/// Gets this channel's region id (if voice channel).
/// </summary>
[JsonProperty("rtc_region", NullValueHandling = NullValueHandling.Ignore)]
internal string RtcRegionId { get; set; }
/// <summary>
/// Gets this channel's region override (if voice channel).
/// </summary>
[JsonIgnore]
public DiscordVoiceRegion RtcRegion
=> this.RtcRegionId != null ? this.Discord.VoiceRegions[this.RtcRegionId] : null;
/// <summary>
/// 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.
/// </summary>
[JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)]
public Permissions? UserPermissions { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordChannel"/> class.
/// </summary>
internal DiscordChannel()
{
this._permissionOverwritesLazy = new Lazy<IReadOnlyList<DiscordOverwrite>>(() => new ReadOnlyCollection<DiscordOverwrite>(this.PermissionOverwritesInternal));
}
#region Methods
/// <summary>
/// Sends a message to this channel.
/// </summary>
/// <param name="content">Content of the message to send.</param>
/// <returns>The sent message.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.SendMessages"/> permission if TTS is true and <see cref="Permissions.SendTtsMessages"/> if TTS is true.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordMessage> SendMessageAsync(string content) =>
!this.IsWritable()
? 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);
/// <summary>
/// Sends a message to this channel.
/// </summary>
/// <param name="embed">Embed to attach to the message.</param>
/// <returns>The sent message.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.SendMessages"/> permission and <see cref="Permissions.SendTtsMessages"/> if TTS is true.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordMessage> SendMessageAsync(DiscordEmbed embed) =>
!this.IsWritable()
? 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);
/// <summary>
/// Sends a message to this channel.
/// </summary>
/// <param name="embed">Embed to attach to the message.</param>
/// <param name="content">Content of the message to send.</param>
/// <returns>The sent message.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.SendMessages"/> permission if TTS is true and <see cref="Permissions.SendTtsMessages"/> if TTS is true.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordMessage> SendMessageAsync(string content, DiscordEmbed embed) =>
!this.IsWritable()
? 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);
/// <summary>
/// Sends a message to this channel.
/// </summary>
/// <param name="builder">The builder with all the items to send.</param>
/// <returns>The sent message.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.SendMessages"/> permission TTS is true and <see cref="Permissions.SendTtsMessages"/> if TTS is true.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordMessage> SendMessageAsync(DiscordMessageBuilder builder)
=> this.Discord.ApiClient.CreateMessageAsync(this.Id, builder);
/// <summary>
/// Sends a message to this channel.
/// </summary>
/// <param name="action">The builder with all the items to send.</param>
/// <returns>The sent message.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.SendMessages"/> permission TTS is true and <see cref="Permissions.SendTtsMessages"/> if TTS is true.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordMessage> SendMessageAsync(Action<DiscordMessageBuilder> action)
{
var builder = new DiscordMessageBuilder();
action(builder);
return !this.IsWritable()
? throw new ArgumentException("Cannot send a text message to a non-text channel.")
: this.Discord.ApiClient.CreateMessageAsync(this.Id, builder);
}
/// <summary>
/// Deletes a guild channel
/// </summary>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageChannels"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task DeleteAsync(string reason = null)
=> this.Discord.ApiClient.DeleteChannelAsync(this.Id, reason);
/// <summary>
/// Clones this channel. This operation will create a channel with identical settings to this one. Note that this will not copy messages or tags.
/// </summary>
/// <param name="reason">Reason for audit logs.</param>
/// <returns>Newly-created channel.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageChannels"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordChannel> CloneAsync(string reason = null)
{
if (this.Guild == null)
throw new InvalidOperationException("Non-guild channels cannot be cloned.");
var ovrs = new List<DiscordOverwriteBuilder>();
foreach (var ovr in this.PermissionOverwritesInternal)
ovrs.Add(await new DiscordOverwriteBuilder().FromAsync(ovr).ConfigureAwait(false));
var bitrate = this.Bitrate;
var userLimit = this.UserLimit;
Optional<int?> perUserRateLimit = this.PerUserRateLimit;
if (!this.IsVoiceJoinable())
{
bitrate = null;
userLimit = null;
}
if (this.Type == ChannelType.Stage)
{
userLimit = null;
}
if (!this.IsWritable())
{
perUserRateLimit = Optional.None;
}
return await this.Guild.CreateChannelAsync(this.Name, this.Type, this.Parent, this.Topic, bitrate, userLimit, ovrs, this.IsNsfw, perUserRateLimit, this.QualityMode, this.DefaultAutoArchiveDuration, this.Flags, reason).ConfigureAwait(false);
}
/// <summary>
/// Gets a specific message.
/// </summary>
/// <param name="id">The id of the message</param>
/// <param name="fetch">Whether to bypass the cache. Defaults to false.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ReadMessageHistory"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordMessage> GetMessageAsync(ulong id, bool fetch = false) =>
this.Discord.Configuration.MessageCacheSize > 0
&& !fetch
&& 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);
/// <summary>
/// Tries to get a specific message.
/// </summary>
/// <param name="id">The id of the message</param>
/// <param name="fetch">Whether to bypass the cache. Defaults to true.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ReadMessageHistory"/> permission.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public async Task<DiscordMessage?> TryGetMessageAsync(ulong id, bool fetch = true)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
try
{
return await this.GetMessageAsync(id, fetch).ConfigureAwait(false);
}
catch (NotFoundException)
{
return null;
}
}
/// <summary>
/// Modifies the current channel.
/// </summary>
/// <param name="action">Action to perform on this channel</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageChannels"/>.</exception>
/// <exception cref="NotSupportedException">Thrown when the client does not have the correct <see cref="PremiumTier"/> for modifying the <see cref="ThreadAutoArchiveDuration"/>.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task ModifyAsync(Action<ChannelEditModel> action)
{
if (this.Type == ChannelType.Forum)
throw new NotSupportedException("Cannot execute this request on a forum channel.");
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")}.");
return this.Discord.ApiClient.ModifyChannelAsync(this.Id, mdl.Name, mdl.Position, mdl.Topic, mdl.Nsfw,
mdl.Parent.Map(p => p?.Id), mdl.Bitrate, mdl.UserLimit, mdl.PerUserRateLimit, mdl.RtcRegion.Map(r => r?.Id),
mdl.QualityMode, mdl.DefaultAutoArchiveDuration, mdl.Type, mdl.PermissionOverwrites, mdl.Flags, mdl.AuditLogReason);
}
/// <summary>
/// Modifies the current forum channel.
/// </summary>
/// <param name="action">Action to perform on this channel</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageChannels"/>.</exception>
/// <exception cref="NotSupportedException">Thrown when the client does not have the correct <see cref="PremiumTier"/> for modifying the <see cref="ThreadAutoArchiveDuration"/>.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task ModifyForumAsync(Action<ForumChannelEditModel> action)
{
if (this.Type != ChannelType.Forum)
throw new NotSupportedException("Cannot execute this request on a non-forum channel.");
var mdl = new ForumChannelEditModel();
action(mdl);
if (mdl.DefaultAutoArchiveDuration.HasValue && mdl.DefaultAutoArchiveDuration.Value.HasValue)
if (!Utilities.CheckThreadAutoArchiveDurationFeature(this.Guild, mdl.DefaultAutoArchiveDuration.Value.Value))
throw new NotSupportedException($"Cannot modify DefaultAutoArchiveDuration. Guild needs boost tier {(mdl.DefaultAutoArchiveDuration.Value == ThreadAutoArchiveDuration.ThreeDays ? "one" : "two")}.");
return mdl.AvailableTags.HasValue && mdl.AvailableTags.Value.Count > 20
? throw new NotSupportedException("Cannot have more than 20 tags in a forum channel.")
: (Task)this.Discord.ApiClient.ModifyForumChannelAsync(this.Id, mdl.Name, mdl.Position, mdl.Topic, mdl.Template, mdl.Nsfw,
mdl.Parent.Map(p => p?.Id), mdl.AvailableTags, mdl.DefaultReactionEmoji, mdl.PerUserRateLimit, mdl.PostCreateUserRateLimit,
mdl.DefaultSortOrder, mdl.DefaultAutoArchiveDuration, mdl.PermissionOverwrites, mdl.Flags, mdl.AuditLogReason);
}
/// <summary>
/// Updates the channel position when it doesn't have a category.
///
/// Use <see cref="ModifyParentAsync"/> for moving to other categories.
/// Use <see cref="RemoveParentAsync"/> to move out of a category.
/// Use <see cref="ModifyPositionInCategoryAsync"/> for moving within a category.
/// </summary>
/// <param name="position">Position the channel should be moved to.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageChannels"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
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 pmds = this.Guild.ChannelsInternal.Values.Where(xc => xc.Type == this.Type).OrderBy(xc => xc.Position)
.Select(x => new RestGuildChannelReorderPayload
{
ChannelId = x.Id,
Position = x.Id == this.Id ? position : x.Position >= position ? x.Position + 1 : x.Position
});
return this.Discord.ApiClient.ModifyGuildChannelPositionAsync(this.Guild.Id, pmds, reason);
}
/// <summary>
/// Updates the channel position within it's own category.
///
/// Use <see cref="ModifyParentAsync"/> for moving to other categories.
/// Use <see cref="RemoveParentAsync"/> to move out of a category.
/// Use <see cref="ModifyPositionAsync"/> to move channels outside a category.
/// </summary>
/// <param name="position">The position.</param>
/// <param name="reason">The reason.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageChannels"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
/// <exception cref="IndexOutOfRangeException">Thrown when <paramref name="position"/> is out of range.</exception>
/// <exception cref="ArgumentException">Thrown when function is called on a channel without a parent channel.</exception>
public async Task ModifyPositionInCategoryAsync(int position, string reason = null)
{
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 = ochns.Select(x =>
new RestGuildChannelReorderPayload
{
ChannelId = x.Id,
Position = x.Id == this.Id
? position
: isUp
? x.Position <= position && x.Position > this.Position ? x.Position - 1 : x.Position
: x.Position >= position && x.Position < this.Position ? x.Position + 1 : x.Position
}
);
await this.Discord.ApiClient.ModifyGuildChannelPositionAsync(this.Guild.Id, pmds, reason).ConfigureAwait(false);
}
/// <summary>
/// Internally refreshes the channel list.
/// </summary>
private async Task<IReadOnlyList<DiscordChannel>> InternalRefreshChannelsAsync()
{
await this.RefreshPositionsAsync();
return this.Guild.Channels.Values.ToList().AsReadOnly();
}
internal void Initialize(BaseDiscordClient client)
{
this.Discord = client;
foreach (var xo in this.PermissionOverwritesInternal)
{
xo.Discord = this.Discord;
xo.ChannelId = this.Id;
}
if (this.InternalAvailableTags != null)
{
foreach (var xo in this.InternalAvailableTags)
{
xo.Discord = this.Discord;
xo.ChannelId = this.Id;
xo.Channel = this;
}
}
}
/// <summary>
/// Refreshes the positions.
/// </summary>
public async Task RefreshPositionsAsync()
{
var channels = await this.Discord.ApiClient.GetGuildChannelsAsync(this.Guild.Id);
this.Guild.ChannelsInternal.Clear();
foreach (var channel in channels.ToList())
{
channel.Initialize(this.Discord);
this.Guild.ChannelsInternal[channel.Id] = channel;
}
}
/// <summary>
/// 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 <see cref="ModifyParentAsync"/> for moving to other categories.
/// Use <see cref="RemoveParentAsync"/> to move out of a category.
/// Use <see cref="ModifyPositionAsync"/> to move channels outside a category.
/// </summary>
/// <param name="mode">The mode. Valid: '+' or 'down' to move a channel down | '-' or 'up' to move a channel up</param>
/// <param name="position">The position.</param>
/// <param name="reason">The reason.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageChannels"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
/// <exception cref="IndexOutOfRangeException">Thrown when <paramref name="position"/> is out of range.</exception>
/// <exception cref="ArgumentException">Thrown when function is called on a channel without a parent channel, a wrong mode is given or given position is zero.</exception>
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.");
}
/// <summary>
/// Updates the channel parent, moving the channel to the bottom of the new category.
/// </summary>
/// <param name="newParent">New parent for channel. Use <see cref="RemoveParentAsync(string)"/> to remove from parent.</param>
/// <param name="lockPermissions">Sync permissions with parent. Defaults to null.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageChannels"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task ModifyParentAsync(DiscordChannel newParent, bool? lockPermissions = null, 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.");
if (newParent.Type is not ChannelType.Category)
throw new ArgumentException("Only category type channels can be parents.");
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 pmds = this.Guild.ChannelsInternal.Values.Where(xc => xc.Type == this.Type)
.OrderBy(xc => xc.Position)
.Select(x =>
{
var pmd = new RestGuildChannelNewParentPayload
{
ChannelId = x.Id,
Position = x.Position >= position ? x.Position + 1 : x.Position,
};
if (x.Id == this.Id)
{
pmd.Position = position;
pmd.ParentId = newParent?.Id;
pmd.LockPermissions = lockPermissions;
}
return pmd;
});
return this.Discord.ApiClient.ModifyGuildChannelParentAsync(this.Guild.Id, pmds, reason);
}
/// <summary>
/// Moves the channel out of a category.
/// </summary>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageChannels"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
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 pmds = this.Guild.ChannelsInternal.Values.Where(xc => xc.Type == this.Type)
.OrderBy(xc => xc.Position)
.Select(x =>
{
var pmd = new RestGuildChannelNoParentPayload { ChannelId = x.Id };
if (x.Id == this.Id)
{
pmd.Position = 1;
pmd.ParentId = null;
}
else
{
pmd.Position = x.Position < this.Position ? x.Position + 1 : x.Position;
}
return pmd;
});
return this.Discord.ApiClient.DetachGuildChannelParentAsync(this.Guild.Id, pmds, reason);
}
/// <summary>
/// Returns a list of messages before a certain message.
/// <param name="limit">The amount of messages to fetch.</param>
/// <param name="before">Message to fetch before from.</param>
/// </summary>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.AccessChannels"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<IReadOnlyList<DiscordMessage>> GetMessagesBeforeAsync(ulong before, int limit = 100)
=> this.GetMessagesInternalAsync(limit, before, null, null);
/// <summary>
/// Returns a list of messages after a certain message.
/// <param name="limit">The amount of messages to fetch.</param>
/// <param name="after">Message to fetch after from.</param>
/// </summary>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.AccessChannels"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<IReadOnlyList<DiscordMessage>> GetMessagesAfterAsync(ulong after, int limit = 100)
=> this.GetMessagesInternalAsync(limit, null, after, null);
/// <summary>
/// Returns a list of messages around a certain message.
/// <param name="limit">The amount of messages to fetch.</param>
/// <param name="around">Message to fetch around from.</param>
/// </summary>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.AccessChannels"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<IReadOnlyList<DiscordMessage>> GetMessagesAroundAsync(ulong around, int limit = 100)
=> this.GetMessagesInternalAsync(limit, null, null, around);
/// <summary>
/// Returns a list of messages from the last message in the channel.
/// <param name="limit">The amount of messages to fetch.</param>
/// </summary>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.AccessChannels"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<IReadOnlyList<DiscordMessage>> GetMessagesAsync(int limit = 100) =>
this.GetMessagesInternalAsync(limit, null, null, null);
/// <summary>
/// Returns a list of messages
/// </summary>
/// <param name="limit">How many messages should be returned.</param>
/// <param name="before">Get messages before snowflake.</param>
/// <param name="after">Get messages after snowflake.</param>
/// <param name="around">Get messages around snowflake.</param>
private async Task<IReadOnlyList<DiscordMessage>> GetMessagesInternalAsync(int limit = 100, ulong? before = null, ulong? after = null, ulong? around = null)
{
if (!this.IsWritable())
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<DiscordMessage>();
//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<DiscordMessage>(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<DiscordMessage>(msgs);
}
/// <summary>
/// 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 <see cref="BadRequestException"/> error.
/// </summary>
/// <param name="messages">A collection of messages to delete.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageMessages"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task DeleteMessagesAsync(IEnumerable<DiscordMessage> 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.Length < 2)
{
await this.Discord.ApiClient.DeleteMessageAsync(this.Id, msgs.Single(), reason).ConfigureAwait(false);
return;
}
for (var i = 0; i < msgs.Length; i += 100)
await this.Discord.ApiClient.DeleteMessagesAsync(this.Id, msgs.Skip(i).Take(100), reason).ConfigureAwait(false);
}
/// <summary>
/// Deletes a message
/// </summary>
/// <param name="message">The message to be deleted.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageMessages"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task DeleteMessageAsync(DiscordMessage message, string reason = null)
=> this.Discord.ApiClient.DeleteMessageAsync(this.Id, message.Id, reason);
/// <summary>
/// Returns a list of invite objects
/// </summary>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.CreateInstantInvite"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<IReadOnlyList<DiscordInvite>> GetInvitesAsync() =>
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);
/// <summary>
/// Create a new invite object
/// </summary>
/// <param name="maxAge">Duration of invite in seconds before expiry, or 0 for never. Defaults to 86400.</param>
/// <param name="maxUses">Max number of uses or 0 for unlimited. Defaults to 0</param>
/// <param name="temporary">Whether this invite should be temporary. Defaults to false.</param>
/// <param name="unique">Whether this invite should be unique. Defaults to false.</param>
/// <param name="targetType">The target type. Defaults to null.</param>
/// <param name="targetApplicationId">The target activity ID. Defaults to null.</param>
/// <param name="targetUser">The target user id. Defaults to null.</param>
/// <param name="reason">The audit log reason.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.CreateInstantInvite"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordInvite> CreateInviteAsync(int maxAge = 86400, int maxUses = 0, bool temporary = false, bool unique = false, TargetType? targetType = null, ulong? targetApplicationId = null, ulong? targetUser = null, string reason = null)
=> this.Discord.ApiClient.CreateChannelInviteAsync(this.Id, maxAge, maxUses, targetType, targetApplicationId, targetUser, temporary, unique, reason);
#region Stage
[Obsolete, DiscordDeprecated("Stage privacy level is removed")]
public async Task<DiscordStageInstance> OpenStageAsync(string topic, bool sendStartNotification = false, StagePrivacyLevel privacyLevel = StagePrivacyLevel.GuildOnly, string reason = null)
=> await this.Discord.ApiClient.CreateStageInstanceAsync(this.Id, topic, sendStartNotification, null, reason);
/// <summary>
/// Opens a stage.
/// </summary>
/// <param name="topic">Topic of the stage.</param>
/// <param name="sendStartNotification">Whether @everyone should be notified.</param>
/// <param name="scheduledEventId">The associated scheduled event id.</param>
/// <param name="reason">Audit log reason.</param>
/// <returns>Stage instance</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageChannels"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordStageInstance> OpenStageAsync(string topic, bool sendStartNotification = false, ulong? scheduledEventId = null, string reason = null)
=> await this.Discord.ApiClient.CreateStageInstanceAsync(this.Id, topic, sendStartNotification, scheduledEventId, reason);
/// <summary>
/// Modifies a stage topic.
/// </summary>
/// <param name="topic">New topic of the stage.</param>
/// <param name="privacyLevel">New privacy level of the stage.</param>
/// <param name="reason">Audit log reason.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageChannels"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
[DiscordDeprecated("Privacy level will be removed in next versions"), Obsolete]
public async Task ModifyStageAsync(Optional<string> topic, Optional<StagePrivacyLevel> privacyLevel, string reason = null)
=> await this.Discord.ApiClient.ModifyStageInstanceAsync(this.Id, topic, reason);
/// <summary>
/// Closes a stage.
/// </summary>
/// <param name="reason">Audit log reason.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageChannels"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task CloseStageAsync(string reason = null)
=> await this.Discord.ApiClient.DeleteStageInstanceAsync(this.Id, reason);
/// <summary>
/// Gets a stage.
/// </summary>
/// <returns>The requested stage.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.AccessChannels"/> or <see cref="Permissions.UseVoice"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordStageInstance> GetStageAsync()
=> await this.Discord.ApiClient.GetStageInstanceAsync(this.Id);
#endregion
#region Scheduled Events
/// <summary>
/// Creates a scheduled event based on the channel type.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="scheduledStartTime">The scheduled start time.</param>
/// <param name="description">The description.</param>
/// <param name="coverImage">The cover image.</param>
/// <param name="reason">The reason.</param>
/// <returns>A scheduled event.</returns>
/// <exception cref="NotFoundException">Thrown when the resource does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordScheduledEvent> CreateScheduledEventAsync(string name, DateTimeOffset scheduledStartTime, string description = null, Optional<Stream> coverImage = default, 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, coverImage, reason);
}
#endregion
#region Threads
/// <summary>
/// Creates a thread.
/// Depending on whether it is created inside an <see cref="ChannelType.News"/> or an <see cref="ChannelType.Text"/> it is either an <see cref="ChannelType.NewsThread"/> or an <see cref="ChannelType.PublicThread"/>.
/// Depending on whether the <see cref="ChannelType"/> is set to <see cref="ChannelType.PrivateThread"/> it is either an <see cref="ChannelType.PrivateThread"/> or an <see cref="ChannelType.PublicThread"/> (default).
/// </summary>
/// <param name="name">The name of the thread.</param>
/// <param name="autoArchiveDuration"><see cref="ThreadAutoArchiveDuration"/> till it gets archived. Defaults to <see cref="ThreadAutoArchiveDuration.OneHour"/>.</param>
/// <param name="type">Can be either an <see cref="ChannelType.PrivateThread"/>, <see cref="ChannelType.NewsThread"/> or an <see cref="ChannelType.PublicThread"/>.</param>
/// <param name="rateLimitPerUser">The per user ratelimit, aka slowdown.</param>
/// <param name="reason">Audit log reason.</param>
/// <returns>The created thread.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.CreatePublicThreads"/> or <see cref="Permissions.SendMessagesInThreads"/> or if creating a private thread the <see cref="Permissions.CreatePrivateThreads"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the guild hasn't enabled threads atm.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
/// <exception cref="NotSupportedException">Thrown when the <see cref="ThreadAutoArchiveDuration"/> cannot be modified. This happens, when the guild hasn't reached a certain boost <see cref="PremiumTier"/>. Or if <see cref="GuildFeaturesEnum.CanCreatePrivateThreads"/> is not enabled for guild. This happens, if the guild does not have <see cref="PremiumTier.TierTwo"/></exception>
public async Task<DiscordThreadChannel> CreateThreadAsync(string name, ThreadAutoArchiveDuration autoArchiveDuration = ThreadAutoArchiveDuration.OneHour, ChannelType type = ChannelType.PublicThread, int? rateLimitPerUser = null, string reason = null) =>
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, autoArchiveDuration)
? await this.Discord.ApiClient.CreateThreadAsync(this.Id, null, name, autoArchiveDuration, type, rateLimitPerUser, isForum: false, reason: 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, autoArchiveDuration)
? await this.Discord.ApiClient.CreateThreadAsync(this.Id, null, name, autoArchiveDuration, this.Type == ChannelType.News ? ChannelType.NewsThread : ChannelType.PublicThread, rateLimitPerUser, isForum: false, reason: reason)
: throw new NotSupportedException($"Cannot modify ThreadAutoArchiveDuration. Guild needs boost tier {(autoArchiveDuration == ThreadAutoArchiveDuration.ThreeDays ? "one" : "two")}.");
/// <summary>
/// Creates a forum post.
/// </summary>
/// <param name="name">The name of the post.</param>
/// <param name="builder">The message of the post.</param>
/// <param name="rateLimitPerUser">The per user ratelimit, aka slowdown.</param>
/// <param name="tags">The tags to add on creation.</param>
/// <param name="reason">Audit log reason.</param>
/// <returns>The created thread.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.SendMessages"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the guild hasn't enabled threads atm.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public async Task<DiscordThreadChannel> CreatePostAsync(string name, DiscordMessageBuilder builder, int? rateLimitPerUser = null, IEnumerable<ForumPostTag>? tags = 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.
=> this.Type != ChannelType.Forum ? throw new NotSupportedException("Parent channel must be forum.") : await this.Discord.ApiClient.CreateThreadAsync(this.Id, null, name, null, null, rateLimitPerUser, tags, builder, true, reason);
/// <summary>
/// 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.
/// </summary>
/// <param name="before">Get threads created before this thread id.</param>
/// <param name="limit">Defines the limit of returned <see cref="DiscordThreadResult"/>.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ReadMessageHistory"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordThreadResult> GetJoinedPrivateArchivedThreadsAsync(ulong? before, int? limit)
=> await this.Discord.ApiClient.GetJoinedPrivateArchivedThreadsAsync(this.Id, before, limit);
/// <summary>
/// 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.
/// </summary>
/// <param name="before">Get threads created before this thread id.</param>
/// <param name="limit">Defines the limit of returned <see cref="DiscordThreadResult"/>.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ReadMessageHistory"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordThreadResult> GetPublicArchivedThreadsAsync(ulong? before, int? limit)
=> await this.Discord.ApiClient.GetPublicArchivedThreadsAsync(this.Id, before, limit);
/// <summary>
/// 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.
/// </summary>
/// <param name="before">Get threads created before this thread id.</param>
/// <param name="limit">Defines the limit of returned <see cref="DiscordThreadResult"/>.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageThreads"/> or <see cref="Permissions.ReadMessageHistory"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordThreadResult> GetPrivateArchivedThreadsAsync(ulong? before, int? limit)
=> await this.Discord.ApiClient.GetPrivateArchivedThreadsAsync(this.Id, before, limit);
/// <summary>
/// Gets a forum channel tag.
/// </summary>
/// <param name="id">The id of the tag to get.</param>
/// <exception cref="InvalidOperationException">Thrown when the tag does not exist.</exception>
public ForumPostTag GetForumPostTag(ulong id)
{
var tag = this.InternalAvailableTags.First(x => x.Id == id);
tag.Discord = this.Discord;
tag.ChannelId = this.Id;
tag.Channel = this;
return tag;
}
/// <summary>
/// Tries to get a forum channel tag.
/// </summary>
/// <param name="id">The id of the tag to get or null if not found.</param>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public ForumPostTag? TryGetForumPostTag(ulong id)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
var tag = this.InternalAvailableTags.FirstOrDefault(x => x.Id == id);
if (tag is not null)
{
tag.Discord = this.Discord;
tag.ChannelId = this.Id;
}
return tag;
}
/// <summary>
/// Creates a forum channel tag.
/// </summary>
/// <param name="name">The name of the tag.</param>
/// <param name="emoji">The emoji of the tag. Has to be either a <see cref="DiscordGuildEmoji"/> of the current guild or a <see cref="DiscordUnicodeEmoji"/>.</param>
/// <param name="moderated">Whether only moderators should be able to apply this tag.</param>
/// <param name="reason">The audit log reason.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageChannels"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the tag does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordChannel> CreateForumPostTagAsync(string name, DiscordEmoji emoji = null, bool moderated = false, string reason = null)
=> this.Type != ChannelType.Forum ? throw new NotSupportedException("Channel needs to be type of Forum") :
this.AvailableTags.Count == 20 ?
throw new NotSupportedException("Cannot have more than 20 tags in a forum channel.") :
await this.Discord.ApiClient.ModifyForumChannelAsync(this.Id, null, null, Optional.None, Optional.None, null, Optional.None, this.InternalAvailableTags.Append(new ForumPostTag()
{
Name = name,
EmojiId = emoji != null && emoji.Id != 0 ? emoji.Id : null,
UnicodeEmojiString = emoji?.Id == null || emoji?.Id == 0 ? emoji?.Name ?? null : null,
Moderated = moderated,
Id = null
}).ToList(), Optional.None, Optional.None, Optional.None, Optional.None, Optional.None, null, Optional.None, reason);
/// <summary>
/// Deletes a forum channel tag.
/// </summary>
/// <param name="id">The id of the tag to delete.</param>
/// <param name="reason">The audit log reason.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageChannels"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the tag does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordChannel> DeleteForumPostTag(ulong id, string reason = null)
=> this.Type != ChannelType.Forum ? throw new NotSupportedException("Channel needs to be type of Forum") : await this.Discord.ApiClient.ModifyForumChannelAsync(this.Id, null, null, Optional.None, Optional.None, null, Optional.None, this.InternalAvailableTags?.Where(x => x.Id != id)?.ToList(), Optional.None, Optional.None, Optional.None, Optional.None, Optional.None, null, Optional.None, reason);
#endregion
/// <summary>
/// Adds a channel permission overwrite for specified role.
/// </summary>
/// <param name="role">The role to have the permission added.</param>
/// <param name="allow">The permissions to allow.</param>
/// <param name="deny">The permissions to deny.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageRoles"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
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);
/// <summary>
/// Adds a channel permission overwrite for specified member.
/// </summary>
/// <param name="member">The member to have the permission added.</param>
/// <param name="allow">The permissions to allow.</param>
/// <param name="deny">The permissions to deny.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageRoles"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
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);
/// <summary>
/// Deletes a channel permission overwrite for specified member.
/// </summary>
/// <param name="member">The member to have the permission deleted.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageRoles"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task DeleteOverwriteAsync(DiscordMember member, string reason = null)
=> this.Discord.ApiClient.DeleteChannelPermissionAsync(this.Id, member.Id, reason);
/// <summary>
/// Deletes a channel permission overwrite for specified role.
/// </summary>
/// <param name="role">The role to have the permission deleted.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageRoles"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task DeleteOverwriteAsync(DiscordRole role, string reason = null)
=> this.Discord.ApiClient.DeleteChannelPermissionAsync(this.Id, role.Id, reason);
/// <summary>
/// Post a typing indicator.
/// </summary>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task TriggerTypingAsync() =>
!this.IsWritable()
? throw new ArgumentException("Cannot start typing in a non-text channel.")
: this.Discord.ApiClient.TriggerTypingAsync(this.Id);
/// <summary>
/// Returns all pinned messages.
/// </summary>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.AccessChannels"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<IReadOnlyList<DiscordMessage>> GetPinnedMessagesAsync() =>
!this.IsWritable()
? throw new ArgumentException("A non-text channel does not have pinned messages.")
: this.Discord.ApiClient.GetPinnedMessagesAsync(this.Id);
/// <summary>
/// Create a new webhook.
/// </summary>
/// <param name="name">The name of the webhook.</param>
/// <param name="avatar">The image for the default webhook avatar.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageWebhooks"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordWebhook> CreateWebhookAsync(string name, Optional<Stream> avatar = default, string reason = null)
=> await this.Discord.ApiClient.CreateWebhookAsync(this.IsThread() ? this.ParentId!.Value : this.Id, name,
ImageTool.Base64FromStream(avatar), reason).ConfigureAwait(false);
/// <summary>
/// Returns a list of webhooks.
/// </summary>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageWebhooks"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<IReadOnlyList<DiscordWebhook>> GetWebhooksAsync()
=> this.Discord.ApiClient.GetChannelWebhooksAsync(this.IsThread() ? this.ParentId!.Value : this.Id);
/// <summary>
/// Moves a member to this voice channel.
/// </summary>
/// <param name="member">The member to be moved.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.MoveMembers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exists or if the Member does not exists.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
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, default, member.MemberFlags, null).ConfigureAwait(false);
}
/// <summary>
/// Follows a news channel.
/// </summary>
/// <param name="targetChannel">Channel to crosspost messages to.</param>
/// <exception cref="ArgumentException">Thrown when trying to follow a non-news channel.</exception>
/// <exception cref="UnauthorizedException">Thrown when the current user doesn't have <see cref="Permissions.ManageWebhooks"/> on the target channel.</exception>
public Task<DiscordFollowedChannel> FollowAsync(DiscordChannel targetChannel) =>
this.Type != ChannelType.News
? throw new ArgumentException("Cannot follow a non-news channel.")
: this.Discord.ApiClient.FollowChannelAsync(this.Id, targetChannel.Id);
/// <summary>
/// Publishes a message in a news channel to following channels.
/// </summary>
/// <param name="message">Message to publish.</param>
/// <exception cref="ArgumentException">Thrown when the message has already been crossposted.</exception>
/// <exception cref="UnauthorizedException">
/// Thrown when the current user doesn't have <see cref="Permissions.ManageWebhooks"/> and/or <see cref="Permissions.SendMessages"/>
/// </exception>
public Task<DiscordMessage> CrosspostMessageAsync(DiscordMessage message) =>
(message.Flags & MessageFlags.Crossposted) == MessageFlags.Crossposted
? throw new ArgumentException("Message is already crossposted.")
: this.Discord.ApiClient.CrosspostMessageAsync(this.Id, message.Id);
/// <summary>
/// Updates the current user's suppress state in this channel, if stage channel.
/// </summary>
/// <param name="suppress">Toggles the suppress state.</param>
/// <param name="requestToSpeakTimestamp">Sets the time the user requested to speak.</param>
/// <exception cref="ArgumentException">Thrown when the channel is not a stage channel.</exception>
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);
}
/// <summary>
/// Calculates permissions for a given member.
/// </summary>
/// <param name="mbr">Member to calculate permissions for.</param>
/// <returns>Calculated permissions for a given member.</returns>
public Permissions PermissionsFor(DiscordMember mbr)
{
// user > role > everyone
// allow > deny > undefined
// =>
// user allow > user deny > role allow > role deny > everyone allow > everyone deny
if (this.IsPrivate || this.Guild == null)
return Permissions.None;
if (this.Guild.OwnerId == mbr.Id)
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);
// assign permissions from member's roles (in order)
perms |= mbRoles.Aggregate(Permissions.None, (c, role) => c | role.Permissions);
// Administrator grants all permissions and cannot be overridden
if ((perms & Permissions.Administrator) == Permissions.Administrator)
return PermissionMethods.FullPerms;
// channel overrides for roles that member is in
var mbRoleOverrides = mbRoles
.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.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.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;
}
/// <summary>
/// Returns a string representation of this channel.
/// </summary>
/// <returns>String representation of this channel.</returns>
public override string ToString() =>
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
/// <summary>
/// Checks whether this <see cref="DiscordChannel"/> is equal to another object.
/// </summary>
/// <param name="obj">Object to compare to.</param>
/// <returns>Whether the object is equal to this <see cref="DiscordChannel"/>.</returns>
public override bool Equals(object obj)
=> this.Equals(obj as DiscordChannel);
/// <summary>
/// Checks whether this <see cref="DiscordChannel"/> is equal to another <see cref="DiscordChannel"/>.
/// </summary>
/// <param name="e"><see cref="DiscordChannel"/> to compare to.</param>
/// <returns>Whether the <see cref="DiscordChannel"/> is equal to this <see cref="DiscordChannel"/>.</returns>
public bool Equals(DiscordChannel e)
=> e is not null && (ReferenceEquals(this, e) || this.Id == e.Id);
/// <summary>
/// Gets the hash code for this <see cref="DiscordChannel"/>.
/// </summary>
/// <returns>The hash code for this <see cref="DiscordChannel"/>.</returns>
public override int GetHashCode()
=> this.Id.GetHashCode();
/// <summary>
/// Gets whether the two <see cref="DiscordChannel"/> objects are equal.
/// </summary>
/// <param name="e1">First channel to compare.</param>
/// <param name="e2">Second channel to compare.</param>
/// <returns>Whether the two channels are equal.</returns>
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);
}
/// <summary>
/// Gets whether the two <see cref="DiscordChannel"/> objects are not equal.
/// </summary>
/// <param name="e1">First channel to compare.</param>
/// <param name="e2">Second channel to compare.</param>
/// <returns>Whether the two channels are not equal.</returns>
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 e5abcdbf0..d94632803 100644
--- a/DisCatSharp/Entities/Channel/DiscordDmChannel.cs
+++ b/DisCatSharp/Entities/Channel/DiscordDmChannel.cs
@@ -1,99 +1,99 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Exceptions;
using DisCatSharp.Net;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a direct message channel.
/// </summary>
public class DiscordDmChannel : DiscordChannel
{
/// <summary>
/// Gets the recipients of this direct message.
/// </summary>
[JsonProperty("recipients", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<DiscordUser> Recipients { get; internal set; }
/// <summary>
/// Gets the hash of this channel's icon.
/// </summary>
[JsonProperty("icon", NullValueHandling = NullValueHandling.Ignore)]
public string IconHash { get; internal set; }
/// <summary>
/// Gets the id of this direct message's creator.
/// </summary>
[JsonProperty("owner_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong OwnerId { get; internal set; }
/// <summary>
/// Gets the application id of the direct message's creator if it a bot.
/// </summary>
[JsonProperty("application_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? ApplicationId { get; internal set; }
/// <summary>
/// Gets whether the channel is managed by an application via the `gdm.join` OAuth2 scope.
/// </summary>
[JsonProperty("managed", NullValueHandling = NullValueHandling.Ignore)]
public bool? Managed { get; internal set; }
-
+
/// <summary>
/// Gets the URL of this channel's icon.
/// </summary>
[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;
/// <summary>
/// Only use for Group DMs! Whitelisted bots only. Requires user's oauth2 access token.
/// </summary>
/// <param name="userId">The id of the user to add.</param>
/// <param name="accessToken">The OAuth2 access token.</param>
/// <param name="nickname">The nickname to give to the user.</param>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task AddDmRecipientAsync(ulong userId, string accessToken, string nickname)
=> this.Discord.ApiClient.AddGroupDmRecipientAsync(this.Id, userId, accessToken, nickname);
/// <summary>
/// Only use for Group DMs! Whitelisted bots only. Requires user's oauth2 access token.
/// </summary>
/// <param name="userId">The id of the User to remove.</param>
/// <param name="accessToken">The OAuth2 access token.</param>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task RemoveDmRecipientAsync(ulong userId, string accessToken)
=> this.Discord.ApiClient.RemoveGroupDmRecipientAsync(this.Id, userId);
}
diff --git a/DisCatSharp/Entities/Channel/DiscordFollowedChannel.cs b/DisCatSharp/Entities/Channel/DiscordFollowedChannel.cs
index 84d9f0d24..b59817354 100644
--- a/DisCatSharp/Entities/Channel/DiscordFollowedChannel.cs
+++ b/DisCatSharp/Entities/Channel/DiscordFollowedChannel.cs
@@ -1,43 +1,43 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a followed channel.
/// </summary>
public class DiscordFollowedChannel
{
/// <summary>
/// Gets the id of the channel following the announcement channel.
/// </summary>
[JsonProperty("channel_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong ChannelId { get; internal set; }
/// <summary>
/// Gets the id of the webhook that posts crossposted messages to the channel.
/// </summary>
[JsonProperty("webhook_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong WebhookId { get; internal set; }
}
diff --git a/DisCatSharp/Entities/Channel/DiscordGuildDirectoryChannel.cs b/DisCatSharp/Entities/Channel/DiscordGuildDirectoryChannel.cs
index ee6a064cd..f93860af9 100644
--- a/DisCatSharp/Entities/Channel/DiscordGuildDirectoryChannel.cs
+++ b/DisCatSharp/Entities/Channel/DiscordGuildDirectoryChannel.cs
@@ -1,95 +1,95 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a discord guild directory channel.
/// </summary>
public class DiscordGuildDirectoryChannel : DiscordChannel, IEquatable<DiscordGuildDirectoryChannel>
{
/// <summary>
/// Initializes a new instance of the <see cref="DiscordGuildDirectoryChannel"/> class.
/// </summary>
internal DiscordGuildDirectoryChannel()
{ }
[JsonIgnore]
public IReadOnlyList<DiscordGuildDirectoryEntry> Entries =>
this.Guild.ChannelsInternal.Values.Where(e => e.ParentId == this.Id).Select(x => x as DiscordGuildDirectoryEntry).ToList();
#region Methods
#endregion
/// <summary>
/// Checks whether this <see cref="DiscordGuildDirectoryChannel"/> is equal to another object.
/// </summary>
/// <param name="obj">Object to compare to.</param>
/// <returns>Whether the object is equal to this <see cref="DiscordGuildDirectoryChannel"/>.</returns>
public override bool Equals(object obj)
=> this.Equals(obj as DiscordGuildDirectoryChannel);
/// <summary>
/// Checks whether this <see cref="DiscordGuildDirectoryChannel"/> is equal to another <see cref="DiscordGuildDirectoryChannel"/>.
/// </summary>
/// <param name="e"><see cref="DiscordGuildDirectoryChannel"/> to compare to.</param>
/// <returns>Whether the <see cref="DiscordGuildDirectoryChannel"/> is equal to this <see cref="DiscordGuildDirectoryChannel"/>.</returns>
public bool Equals(DiscordGuildDirectoryChannel e)
=> e is not null && (ReferenceEquals(this, e) || this.Id == e.Id);
/// <summary>
/// Gets the hash code for this <see cref="DiscordGuildDirectoryChannel"/>.
/// </summary>
/// <returns>The hash code for this <see cref="DiscordGuildDirectoryChannel"/>.</returns>
public override int GetHashCode()
=> this.Id.GetHashCode();
/// <summary>
/// Gets whether the two <see cref="DiscordGuildDirectoryChannel"/> objects are equal.
/// </summary>
/// <param name="e1">First channel to compare.</param>
/// <param name="e2">Second channel to compare.</param>
/// <returns>Whether the two channels are equal.</returns>
public static bool operator ==(DiscordGuildDirectoryChannel e1, DiscordGuildDirectoryChannel 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);
}
/// <summary>
/// Gets whether the two <see cref="DiscordGuildDirectoryChannel"/> objects are not equal.
/// </summary>
/// <param name="e1">First channel to compare.</param>
/// <param name="e2">Second channel to compare.</param>
/// <returns>Whether the two channels are not equal.</returns>
public static bool operator !=(DiscordGuildDirectoryChannel e1, DiscordGuildDirectoryChannel e2)
=> !(e1 == e2);
}
diff --git a/DisCatSharp/Entities/Channel/DiscordGuildDirectoryEntry.cs b/DisCatSharp/Entities/Channel/DiscordGuildDirectoryEntry.cs
index ce04c1f56..45fd919df 100644
--- a/DisCatSharp/Entities/Channel/DiscordGuildDirectoryEntry.cs
+++ b/DisCatSharp/Entities/Channel/DiscordGuildDirectoryEntry.cs
@@ -1,104 +1,104 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a discord guild directory channel.
/// </summary>
public class DiscordGuildDirectoryEntry : DiscordChannel, IEquatable<DiscordGuildDirectoryEntry>
{
/// <summary>
/// Gets the description of the directory entry.
/// </summary>
[JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)]
public string Description { get; internal set; }
/// <summary>
/// Gets the primary category of the directory entry.
/// </summary>
[JsonProperty("primary_category_id", NullValueHandling = NullValueHandling.Ignore)]
public DirectoryCategory PrimaryCategory { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordGuildDirectoryEntry"/> class.
/// </summary>
internal DiscordGuildDirectoryEntry()
{ }
#region Methods
#endregion
/// <summary>
/// Checks whether this <see cref="DiscordGuildDirectoryEntry"/> is equal to another object.
/// </summary>
/// <param name="obj">Object to compare to.</param>
/// <returns>Whether the object is equal to this <see cref="DiscordGuildDirectoryEntry"/>.</returns>
public override bool Equals(object obj)
=> this.Equals(obj as DiscordGuildDirectoryEntry);
/// <summary>
/// Checks whether this <see cref="DiscordGuildDirectoryEntry"/> is equal to another <see cref="DiscordGuildDirectoryEntry"/>.
/// </summary>
/// <param name="e"><see cref="DiscordGuildDirectoryEntry"/> to compare to.</param>
/// <returns>Whether the <see cref="DiscordGuildDirectoryEntry"/> is equal to this <see cref="DiscordGuildDirectoryEntry"/>.</returns>
public bool Equals(DiscordGuildDirectoryEntry e)
=> e is not null && (ReferenceEquals(this, e) || this.Id == e.Id);
/// <summary>
/// Gets the hash code for this <see cref="DiscordGuildDirectoryEntry"/>.
/// </summary>
/// <returns>The hash code for this <see cref="DiscordGuildDirectoryEntry"/>.</returns>
public override int GetHashCode()
=> this.Id.GetHashCode();
/// <summary>
/// Gets whether the two <see cref="DiscordGuildDirectoryEntry"/> objects are equal.
/// </summary>
/// <param name="e1">First channel to compare.</param>
/// <param name="e2">Second channel to compare.</param>
/// <returns>Whether the two channels are equal.</returns>
public static bool operator ==(DiscordGuildDirectoryEntry e1, DiscordGuildDirectoryEntry 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);
}
/// <summary>
/// Gets whether the two <see cref="DiscordGuildDirectoryEntry"/> objects are not equal.
/// </summary>
/// <param name="e1">First channel to compare.</param>
/// <param name="e2">Second channel to compare.</param>
/// <returns>Whether the two channels are not equal.</returns>
public static bool operator !=(DiscordGuildDirectoryEntry e1, DiscordGuildDirectoryEntry e2)
=> !(e1 == e2);
}
diff --git a/DisCatSharp/Entities/Channel/Overwrite/DiscordOverwrite.cs b/DisCatSharp/Entities/Channel/Overwrite/DiscordOverwrite.cs
index f8a017b18..b02622eb2 100644
--- a/DisCatSharp/Entities/Channel/Overwrite/DiscordOverwrite.cs
+++ b/DisCatSharp/Entities/Channel/Overwrite/DiscordOverwrite.cs
@@ -1,126 +1,126 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Threading.Tasks;
using DisCatSharp.Enums;
using DisCatSharp.Exceptions;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a permission overwrite for a channel.
/// </summary>
public class DiscordOverwrite : SnowflakeObject
{
/// <summary>
/// Gets the type of the overwrite. Either "role" or "member".
/// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
public OverwriteType Type { get; internal set; }
/// <summary>
/// Gets the allowed permission set.
/// </summary>
[JsonProperty("allow", NullValueHandling = NullValueHandling.Ignore)]
public Permissions Allowed { get; internal set; }
/// <summary>
/// Gets the denied permission set.
/// </summary>
[JsonProperty("deny", NullValueHandling = NullValueHandling.Ignore)]
public Permissions Denied { get; internal set; }
[JsonIgnore]
internal ulong ChannelId;
#region Methods
/// <summary>
/// Deletes this channel overwrite.
/// </summary>
/// <param name="reason">Reason as to why this overwrite gets deleted.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageRoles"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the overwrite does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task DeleteAsync(string reason = null) => this.Discord.ApiClient.DeleteChannelPermissionAsync(this.ChannelId, this.Id, reason);
-
+
/// <summary>
/// Updates this channel overwrite.
/// </summary>
/// <param name="allow">Permissions that are allowed.</param>
/// <param name="deny">Permissions that are denied.</param>
/// <param name="reason">Reason as to why you made this change.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageRoles"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the overwrite does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task UpdateAsync(Permissions? allow = null, Permissions? deny = null, string reason = null)
=> this.Discord.ApiClient.EditChannelPermissionsAsync(this.ChannelId, this.Id, allow ?? this.Allowed, deny ?? this.Denied, this.Type.ToString().ToLowerInvariant(), reason);
/// <summary>
/// Gets the DiscordMember that is affected by this overwrite.
/// </summary>
/// <returns>The DiscordMember that is affected by this overwrite</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.AccessChannels"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the overwrite does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordMember> GetMemberAsync() =>
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.ChannelId).ConfigureAwait(false)).Guild.GetMemberAsync(this.Id).ConfigureAwait(false);
/// <summary>
/// Gets the DiscordRole that is affected by this overwrite.
/// </summary>
/// <returns>The DiscordRole that is affected by this overwrite</returns>
/// <exception cref="NotFoundException">Thrown when the role does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordRole> GetRoleAsync() =>
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.ChannelId).ConfigureAwait(false)).Guild.GetRole(this.Id);
#endregion
/// <summary>
/// Initializes a new instance of the <see cref="DiscordOverwrite"/> class.
/// </summary>
internal DiscordOverwrite()
{ }
/// <summary>
/// Checks whether given permissions are allowed, denied, or not set.
/// </summary>
/// <param name="permission">Permissions to check.</param>
/// <returns>Whether given permissions are allowed, denied, or not set.</returns>
public PermissionLevel CheckPermission(Permissions permission) =>
(this.Allowed & permission) != 0
? PermissionLevel.Allowed
: (this.Denied & permission) != 0 ? PermissionLevel.Denied : PermissionLevel.Unset;
}
diff --git a/DisCatSharp/Entities/Channel/Overwrite/DiscordOverwriteBuilder.cs b/DisCatSharp/Entities/Channel/Overwrite/DiscordOverwriteBuilder.cs
index 80f8c424e..245cea78c 100644
--- a/DisCatSharp/Entities/Channel/Overwrite/DiscordOverwriteBuilder.cs
+++ b/DisCatSharp/Entities/Channel/Overwrite/DiscordOverwriteBuilder.cs
@@ -1,180 +1,180 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Threading.Tasks;
using DisCatSharp.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a Discord permission overwrite builder.
/// </summary>
public sealed class DiscordOverwriteBuilder
{
/// <summary>
/// Gets or sets the allowed permissions for this overwrite.
/// </summary>
public Permissions Allowed { get; set; }
/// <summary>
/// Gets or sets the denied permissions for this overwrite.
/// </summary>
public Permissions Denied { get; set; }
/// <summary>
/// Gets or sets the type of this overwrite's target.
/// </summary>
public OverwriteType Type { get; set; }
/// <summary>
/// Gets or sets the target for this overwrite.
/// </summary>
public SnowflakeObject Target { get; set; }
/// <summary>
/// Creates a new Discord permission overwrite builder for a member. This class can be used to construct permission overwrites for guild channels, used when creating channels.
/// </summary>
public DiscordOverwriteBuilder(DiscordMember member)
{
this.Target = member;
this.Type = OverwriteType.Member;
}
/// <summary>
/// Creates a new Discord permission overwrite builder for a role. This class can be used to construct permission overwrites for guild channels, used when creating channels.
/// </summary>
public DiscordOverwriteBuilder(DiscordRole role)
{
this.Target = role;
this.Type = OverwriteType.Role;
}
/// <summary>
/// Creates a new Discord permission overwrite builder. This class can be used to construct permission overwrites for guild channels, used when creating channels.
/// </summary>
public DiscordOverwriteBuilder()
{ }
/// <summary>
/// Allows a permission for this overwrite.
/// </summary>
/// <param name="permission">Permission or permission set to allow for this overwrite.</param>
/// <returns>This builder.</returns>
public DiscordOverwriteBuilder Allow(Permissions permission)
{
this.Allowed |= permission;
return this;
}
/// <summary>
/// Denies a permission for this overwrite.
/// </summary>
/// <param name="permission">Permission or permission set to deny for this overwrite.</param>
/// <returns>This builder.</returns>
public DiscordOverwriteBuilder Deny(Permissions permission)
{
this.Denied |= permission;
return this;
}
/// <summary>
/// Sets the member to which this overwrite applies.
/// </summary>
/// <param name="member">Member to which apply this overwrite's permissions.</param>
/// <returns>This builder.</returns>
public DiscordOverwriteBuilder For(DiscordMember member)
{
this.Target = member;
this.Type = OverwriteType.Member;
return this;
}
/// <summary>
/// Sets the role to which this overwrite applies.
/// </summary>
/// <param name="role">Role to which apply this overwrite's permissions.</param>
/// <returns>This builder.</returns>
public DiscordOverwriteBuilder For(DiscordRole role)
{
this.Target = role;
this.Type = OverwriteType.Role;
return this;
}
/// <summary>
/// Populates this builder with data from another overwrite object.
/// </summary>
/// <param name="other">Overwrite from which data will be used.</param>
/// <returns>This builder.</returns>
public async Task<DiscordOverwriteBuilder> FromAsync(DiscordOverwrite other)
{
this.Allowed = other.Allowed;
this.Denied = other.Denied;
this.Type = other.Type;
this.Target = this.Type == OverwriteType.Member ? await other.GetMemberAsync().ConfigureAwait(false) : await other.GetRoleAsync().ConfigureAwait(false);
return this;
}
/// <summary>
/// Builds this DiscordOverwrite.
/// </summary>
/// <returns>Use this object for creation of new overwrites.</returns>
internal DiscordRestOverwrite Build() =>
new()
{
Allow = this.Allowed,
Deny = this.Denied,
Id = this.Target.Id,
Type = this.Type,
};
}
internal struct DiscordRestOverwrite
{
/// <summary>
/// Determines what is allowed.
/// </summary>
[JsonProperty("allow", NullValueHandling = NullValueHandling.Ignore)]
internal Permissions Allow { get; set; }
/// <summary>
/// Determines what is denied.
/// </summary>
[JsonProperty("deny", NullValueHandling = NullValueHandling.Ignore)]
internal Permissions Deny { get; set; }
/// <summary>
/// Gets or sets the id.
/// </summary>
[JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)]
internal ulong Id { get; set; }
/// <summary>
/// Gets or sets the overwrite type.
/// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
internal OverwriteType Type { get; set; }
}
diff --git a/DisCatSharp/Entities/Color/DiscordColor.Colors.cs b/DisCatSharp/Entities/Color/DiscordColor.Colors.cs
index 82542b1c9..10c9f940c 100644
--- a/DisCatSharp/Entities/Color/DiscordColor.Colors.cs
+++ b/DisCatSharp/Entities/Color/DiscordColor.Colors.cs
@@ -1,242 +1,242 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
public readonly partial struct DiscordColor
{
#region Black and White
/// <summary>
/// Represents no color, or integer 0;
/// </summary>
public static DiscordColor None { get; } = new(0);
/// <summary>
/// A near-black color. Due to API limitations, the color is #010101, rather than #000000, as the latter is treated as no color.
/// </summary>
public static DiscordColor Black { get; } = new(0x010101);
/// <summary>
/// White, or #FFFFFF.
/// </summary>
public static DiscordColor White { get; } = new(0xFFFFFF);
/// <summary>
/// Gray, or #808080.
/// </summary>
public static DiscordColor Gray { get; } = new(0x808080);
/// <summary>
/// Dark gray, or #A9A9A9.
/// </summary>
public static DiscordColor DarkGray { get; } = new(0xA9A9A9);
/// <summary>
/// Light gray, or #808080.
/// </summary>
public static DiscordColor LightGray { get; } = new(0xD3D3D3);
/// <summary>
/// Very dark gray, or #666666.
/// </summary>
public static DiscordColor VeryDarkGray { get; } = new(0x666666);
#endregion
#region Discord branding colors
// See https://discord.com/branding.
/// <summary>
/// Discord Blurple, or #5865F2.
/// </summary>
public static DiscordColor Blurple { get; } = new(0x5865F2);
/// <summary>
/// Discord Fuchsia, or #EB459E.
/// </summary>
public static DiscordColor Fuchsia { get; } = new(0xEB459E);
/// <summary>
/// Discord Green, or #57F287.
/// </summary>
public static DiscordColor Green { get; } = new(0x57F287);
/// <summary>
/// Discord Yellow, or #FEE75C.
/// </summary>
public static DiscordColor Yellow { get; } = new(0xFEE75C);
/// <summary>
/// Discord Red, or #ED4245.
/// </summary>
public static DiscordColor Red { get; } = new(0xED4245);
#endregion
#region Other colors
/// <summary>
/// Dark red, or #7F0000.
/// </summary>
public static DiscordColor DarkRed { get; } = new(0x7F0000);
/// <summary>
/// Dark green, or #007F00.
/// </summary>
public static DiscordColor DarkGreen { get; } = new(0x007F00);
/// <summary>
/// Blue, or #0000FF.
/// </summary>
public static DiscordColor Blue { get; } = new(0x0000FF);
/// <summary>
/// Dark blue, or #00007F.
/// </summary>
public static DiscordColor DarkBlue { get; } = new(0x00007F);
/// <summary>
/// Cyan, or #00FFFF.
/// </summary>
public static DiscordColor Cyan { get; } = new(0x00FFFF);
/// <summary>
/// Magenta, or #FF00FF.
/// </summary>
public static DiscordColor Magenta { get; } = new(0xFF00FF);
/// <summary>
/// Teal, or #008080.
/// </summary>
public static DiscordColor Teal { get; } = new(0x008080);
// meme
/// <summary>
/// Aquamarine, or #00FFBF.
/// </summary>
public static DiscordColor Aquamarine { get; } = new(0x00FFBF);
/// <summary>
/// Gold, or #FFD700.
/// </summary>
public static DiscordColor Gold { get; } = new(0xFFD700);
/// <summary>
/// Goldenrod, or #DAA520.
/// </summary>
public static DiscordColor Goldenrod { get; } = new(0xDAA520);
/// <summary>
/// Azure, or #007FFF.
/// </summary>
public static DiscordColor Azure { get; } = new(0x007FFF);
/// <summary>
/// Rose, or #FF007F.
/// </summary>
public static DiscordColor Rose { get; } = new(0xFF007F);
/// <summary>
/// Spring green, or #00FF7F.
/// </summary>
public static DiscordColor SpringGreen { get; } = new(0x00FF7F);
/// <summary>
/// Chartreuse, or #7FFF00.
/// </summary>
public static DiscordColor Chartreuse { get; } = new(0x7FFF00);
/// <summary>
/// Orange, or #FFA500.
/// </summary>
public static DiscordColor Orange { get; } = new(0xFFA500);
/// <summary>
/// Purple, or #800080.
/// </summary>
public static DiscordColor Purple { get; } = new(0x800080);
/// <summary>
/// Violet, or #EE82EE.
/// </summary>
public static DiscordColor Violet { get; } = new(0xEE82EE);
/// <summary>
/// Brown, or #A52A2A.
/// </summary>
public static DiscordColor Brown { get; } = new(0xA52A2A);
/// <summary>
/// Hot pink, or #FF69B4
/// </summary>
public static DiscordColor HotPink { get; } = new(0xFF69B4);
/// <summary>
/// Lilac, or #C8A2C8.
/// </summary>
public static DiscordColor Lilac { get; } = new(0xC8A2C8);
/// <summary>
/// Cornflower blue, or #6495ED.
/// </summary>
public static DiscordColor CornflowerBlue { get; } = new(0x6495ED);
/// <summary>
/// Midnight blue, or #191970.
/// </summary>
public static DiscordColor MidnightBlue { get; } = new(0x191970);
/// <summary>
/// Wheat, or #F5DEB3.
/// </summary>
public static DiscordColor Wheat { get; } = new(0xF5DEB3);
/// <summary>
/// Indian red, or #CD5C5C.
/// </summary>
public static DiscordColor IndianRed { get; } = new(0xCD5C5C);
/// <summary>
/// Turquoise, or #30D5C8.
/// </summary>
public static DiscordColor Turquoise { get; } = new(0x30D5C8);
/// <summary>
/// Sap green, or #507D2A.
/// </summary>
public static DiscordColor SapGreen { get; } = new(0x507D2A);
// meme, specifically bob ross
/// <summary>
/// Phthalo blue, or #000F89.
/// </summary>
public static DiscordColor PhthaloBlue { get; } = new(0x000F89);
// meme, specifically bob ross
/// <summary>
/// Phthalo green, or #123524.
/// </summary>
public static DiscordColor PhthaloGreen { get; } = new(0x123524);
/// <summary>
/// Sienna, or #882D17.
/// </summary>
public static DiscordColor Sienna { get; } = new(0x882D17);
#endregion
}
diff --git a/DisCatSharp/Entities/Color/DiscordColor.cs b/DisCatSharp/Entities/Color/DiscordColor.cs
index 0a0c4ef97..f3a3af9e9 100644
--- a/DisCatSharp/Entities/Color/DiscordColor.cs
+++ b/DisCatSharp/Entities/Color/DiscordColor.cs
@@ -1,129 +1,129 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a color used in Discord API.
/// </summary>
public partial struct DiscordColor
{
private static readonly char[] s_hexAlphabet = new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
/// <summary>
/// Gets the integer representation of this color.
/// </summary>
public int Value { get; }
/// <summary>
/// Gets the red component of this color as an 8-bit integer.
/// </summary>
public byte R
=> (byte)((this.Value >> 16) & 0xFF);
/// <summary>
/// Gets the green component of this color as an 8-bit integer.
/// </summary>
public byte G
=> (byte)((this.Value >> 8) & 0xFF);
/// <summary>
/// Gets the blue component of this color as an 8-bit integer.
/// </summary>
public byte B
=> (byte)(this.Value & 0xFF);
/// <summary>
/// Creates a new color with specified value.
/// </summary>
/// <param name="color">Value of the color.</param>
public DiscordColor(int color)
{
this.Value = color;
}
/// <summary>
/// Creates a new color with specified values for red, green, and blue components.
/// </summary>
/// <param name="r">Value of the red component.</param>
/// <param name="g">Value of the green component.</param>
/// <param name="b">Value of the blue component.</param>
public DiscordColor(byte r, byte g, byte b)
{
this.Value = (r << 16) | (g << 8) | b;
}
/// <summary>
/// Creates a new color with specified values for red, green, and blue components.
/// </summary>
/// <param name="r">Value of the red component.</param>
/// <param name="g">Value of the green component.</param>
/// <param name="b">Value of the blue component.</param>
public DiscordColor(float r, float g, float b)
{
if (r < 0 || r > 1 || g < 0 || g > 1 || b < 0 || b > 1)
throw new ArgumentOutOfRangeException(null, "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;
}
/// <summary>
/// Creates a new color from specified string representation.
/// </summary>
/// <param name="color">String representation of the color. Must be 6 hexadecimal characters, optionally with # prefix.</param>
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("Color must be 6 or 7 characters in length.", nameof(color));
color = color.ToUpper();
if (color.Length == 7 && color[0] != '#')
throw new ArgumentException("7-character colors must begin with #.", nameof(color));
else if (color.Length == 7)
color = color[1..];
if (color.Any(xc => !s_hexAlphabet.Contains(xc)))
throw new ArgumentException("Colors must consist of hexadecimal characters only.", nameof(color));
this.Value = int.Parse(color, NumberStyles.HexNumber, CultureInfo.InvariantCulture);
}
/// <summary>
/// Gets a string representation of this color.
/// </summary>
/// <returns>String representation of this color.</returns>
public override string ToString() => $"#{this.Value:X6}";
public static implicit operator DiscordColor(int value)
=> new(value);
}
diff --git a/DisCatSharp/Entities/DCS/DisCatSharpTeam.cs b/DisCatSharp/Entities/DCS/DisCatSharpTeam.cs
index 4a703c1f7..9a8915595 100644
--- a/DisCatSharp/Entities/DCS/DisCatSharpTeam.cs
+++ b/DisCatSharp/Entities/DCS/DisCatSharpTeam.cs
@@ -1,206 +1,206 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net.Http;
using System.Threading.Tasks;
using DisCatSharp.Enums;
using DisCatSharp.Net;
using DisCatSharp.Net.Abstractions;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// The DisCatSharp team.
/// </summary>
public sealed class DisCatSharpTeam : SnowflakeObject
{
/// <summary>
/// Gets the team's name.
/// </summary>
public string TeamName { get; internal set; }
/// <summary>
/// Gets the overall owner.
/// </summary>
public string MainOwner
=> "Lala Sabathil";
/// <summary>
/// Gets the team's icon.
/// </summary>
public string Icon
=> !string.IsNullOrWhiteSpace(this.IconHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.TEAM_ICONS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.IconHash}.png?size=1024" : null;
/// <summary>
/// Gets the team's icon's hash.
/// </summary>
public string IconHash { get; internal set; }
/// <summary>
/// Gets the team's logo.
/// </summary>
public string Logo
=> !string.IsNullOrWhiteSpace(this.LogoHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.ICONS}/{this.GuildId.ToString(CultureInfo.InvariantCulture)}/{this.LogoHash}.png?size=1024" : null;
/// <summary>
/// Gets the team's logo's hash.
/// </summary>
public string LogoHash { get; internal set; }
/// <summary>
/// Gets the team's banner.
/// </summary>
public string Banner
=> !string.IsNullOrWhiteSpace(this.BannerHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.BANNERS}/{this.GuildId.ToString(CultureInfo.InvariantCulture)}/{this.BannerHash}.png?size=1024" : null;
/// <summary>
/// Gets the team's banner's hash.
/// </summary>
public string BannerHash { get; internal set; }
/// <summary>
/// Gets the team's docs url.
/// </summary>
public string DocsUrl { get; internal set; }
/// <summary>
/// Gets the team's repo url.
/// </summary>
public string RepoUrl { get; internal set; }
/// <summary>
/// Gets the team's terms of service url.
/// </summary>
public string TermsOfServiceUrl { get; internal set; }
/// <summary>
/// Gets the team's privacy policy url.
/// </summary>
public string PrivacyPolicyUrl { get; internal set; }
/// <summary>
/// Get's the team's guild id
/// </summary>
public ulong GuildId { get; internal set; }
/// <summary>
/// Gets the team's developers.
/// </summary>
public IReadOnlyList<DisCatSharpTeamMember> Developers { get; internal set; }
/// <summary>
/// Gets the team's owner.
/// </summary>
public DisCatSharpTeamMember Owner { get; internal set; }
/// <summary>
/// Gets the team's guild.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the team's support invite.
/// </summary>
public DiscordInvite SupportInvite { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DisCatSharpTeam"/> class.
/// </summary>
internal static async Task<DisCatSharpTeam> Get(HttpClient http, ILogger logger, DiscordApiClient apiClient)
{
try
{
var dcs = await http.GetStringAsync(new Uri("https://dcs.aitsys.dev/api/devs/"));
var dcsGuild = await http.GetStringAsync(new Uri("https://dcs.aitsys.dev/api/guild/"));
var app = JsonConvert.DeserializeObject<TransportApplication>(dcs);
var guild = JsonConvert.DeserializeObject<DiscordGuild>(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 apiClient.GetInviteAsync("GGYSywkxwN", true, true, null)
};
List<DisCatSharpTeamMember> team = new();
DisCatSharpTeamMember owner = new();
foreach (var mb in app.Team.Members.OrderBy(m => m.User.Username))
{
var tuser = await apiClient.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.BannerColorInternal = tuser.BannerColorInternal;
team.Add(owner);
}
else
{
team.Add(new DisCatSharpTeamMember
{
Id = user.Id,
Username = user.Username,
Discriminator = user.Discriminator,
AvatarHash = user.AvatarHash,
BannerHash = tuser.BannerHash,
BannerColorInternal = tuser.BannerColorInternal
});
}
}
dcst.Owner = owner;
dcst.Developers = team;
return dcst;
}
catch (Exception ex)
{
logger.LogDebug(ex.Message);
logger.LogDebug(ex.StackTrace);
return null;
}
}
private DisCatSharpTeam() { }
}
diff --git a/DisCatSharp/Entities/DCS/DisCatSharpTeamMember.cs b/DisCatSharp/Entities/DCS/DisCatSharpTeamMember.cs
index fdecfd22b..f08d9edad 100644
--- a/DisCatSharp/Entities/DCS/DisCatSharpTeamMember.cs
+++ b/DisCatSharp/Entities/DCS/DisCatSharpTeamMember.cs
@@ -1,92 +1,92 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a DisCatSharp team member.
/// </summary>
public sealed class DisCatSharpTeamMember : SnowflakeObject
{
/// <summary>
/// Gets this user's username.
/// </summary>
public string Username { get; internal set; }
/// <summary>
/// Gets the user's 4-digit discriminator.
/// </summary>
public string Discriminator { get; internal set; }
/// <summary>
/// Gets the discriminator integer.
/// </summary>
internal int DiscriminatorInt
=> int.Parse(this.Discriminator, NumberStyles.Integer, CultureInfo.InvariantCulture);
/// <summary>
/// Gets the user's banner color, if set. Mutually exclusive with <see cref="BannerHash"/>.
/// </summary>
public DiscordColor? BannerColor
=> !this.BannerColorInternal.HasValue ? null : new DiscordColor(this.BannerColorInternal.Value);
internal int? BannerColorInternal;
/// <summary>
/// Gets the user's banner url
/// </summary>
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";
/// <summary>
/// Gets the user's profile banner hash. Mutually exclusive with <see cref="BannerColor"/>.
/// </summary>
public string BannerHash { get; internal set; }
/// <summary>
/// Gets the user's avatar hash.
/// </summary>
public string AvatarHash { get; internal set; }
/// <summary>
/// Gets the user's avatar URL.
/// </summary>
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";
/// <summary>
/// Gets the URL of default avatar for this user.
/// </summary>
public string DefaultAvatarUrl
=> $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.EMBED}{Endpoints.AVATARS}/{(this.DiscriminatorInt % 5).ToString(CultureInfo.InvariantCulture)}.png?size=1024";
/// <summary>
/// Initializes a new instance of the <see cref="DisCatSharpTeamMember"/> class.
/// </summary>
internal DisCatSharpTeamMember()
{ }
}
diff --git a/DisCatSharp/Entities/DiscordLocales.cs b/DisCatSharp/Entities/DiscordLocales.cs
index 59b15f716..fbe99ecde 100644
--- a/DisCatSharp/Entities/DiscordLocales.cs
+++ b/DisCatSharp/Entities/DiscordLocales.cs
@@ -1,57 +1,57 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
public static class DiscordLocales
{
public const string DANISH = "da";
public const string GERMAN = "de";
public const string BRITISH_ENGLISH = "en-GB";
public const string AMERICAN_ENGLISH = "en-US";
public const string SPANISH = "es-ES";
public const string FRENCH = "fr";
public const string CROATIAN = "hr";
public const string ITALIAN = "it";
public const string LITHUANIAN = "lt";
public const string HUNGARIAN = "hu";
public const string DUTCH = "nl";
public const string NORWEGIAN = "no";
public const string POLISH = "pl";
public const string PORTUGUESE_BRAZILIAN = "pt-BR";
public const string ROMANIAN = "ro";
public const string FINNISH = "fi";
public const string SWEDISH = "sv-SE";
public const string VIETNAMESE = "vi";
public const string TURKISH = "tr";
public const string CZECH = "cs";
public const string GREEK = "el";
public const string BULGARIAN = "bg";
public const string RUSSIAN = "ru";
public const string UKRAINIAN = "uk";
public const string HINDI = "hi";
public const string THAI = "th";
public const string CHINESE_CHINA = "zh-CN";
public const string JAPANESE = "ja";
public const string CHINESE_TAIWAN = "zh-TW";
public const string KOREAN = "ko";
}
diff --git a/DisCatSharp/Entities/DiscordProtocol.cs b/DisCatSharp/Entities/DiscordProtocol.cs
index ca2c2db33..3fe30457d 100644
--- a/DisCatSharp/Entities/DiscordProtocol.cs
+++ b/DisCatSharp/Entities/DiscordProtocol.cs
@@ -1,43 +1,43 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents the discord protocol.
/// </summary>
public class DiscordProtocol
{
#region Properties
#endregion
/// <summary>
/// Initializes a new instance of the <see cref="DiscordProtocol"/> class.
/// </summary>
public DiscordProtocol()
{ }
#region Methods
#endregion
}
diff --git a/DisCatSharp/Entities/DiscordUri.cs b/DisCatSharp/Entities/DiscordUri.cs
index 3ef455f2d..ebfbe22d2 100644
--- a/DisCatSharp/Entities/DiscordUri.cs
+++ b/DisCatSharp/Entities/DiscordUri.cs
@@ -1,161 +1,161 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Runtime.CompilerServices;
using Newtonsoft.Json;
namespace DisCatSharp.Net;
/// <summary>
/// An URI in a Discord embed doesn't necessarily conform to the RFC 3986. If it uses the <c>attachment://</c>
/// protocol, it mustn't contain a trailing slash to be interpreted correctly as an embed attachment reference by
/// Discord.
/// </summary>
[JsonConverter(typeof(DiscordUriJsonConverter))]
public class DiscordUri
{
private readonly object _value;
/// <summary>
/// The type of this URI.
/// </summary>
public DiscordUriType Type { get; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordUri"/> class.
/// </summary>
/// <param name="value">The value.</param>
internal DiscordUri(Uri value)
{
this._value = value ?? throw new ArgumentNullException(nameof(value));
this.Type = DiscordUriType.Standard;
}
/// <summary>
/// Initializes a new instance of the <see cref="DiscordUri"/> class.
/// </summary>
/// <param name="value">The value.</param>
internal DiscordUri(string value)
{
if (value == null)
throw new ArgumentNullException(nameof(value));
if (IsStandard(value))
{
this._value = new Uri(value);
this.Type = DiscordUriType.Standard;
}
else
{
this._value = value;
this.Type = DiscordUriType.NonStandard;
}
}
/// <summary>
/// Whether the uri is a standard uri
/// </summary>
/// <param name="value">Uri string</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsStandard(string value) => !value.StartsWith("attachment://");
/// <summary>
/// Returns a string representation of this DiscordUri.
/// </summary>
/// <returns>This DiscordUri, as a string.</returns>
public override string ToString() => this._value.ToString();
/// <summary>
/// Converts this DiscordUri into a canonical representation of a <see cref="Uri"/> if it can be represented as
/// such, throwing an exception otherwise.
/// </summary>
/// <returns>A canonical representation of this DiscordUri.</returns>
/// <exception cref="UriFormatException">If <see cref="System.Type"/> is not <see cref="DiscordUriType.Standard"/>, as
/// that would mean creating an invalid Uri, which would result in loss of data.</exception>
public Uri ToUri()
=> this.Type == DiscordUriType.Standard
? this._value as Uri
: throw new UriFormatException(
$@"DiscordUri ""{this._value}"" would be invalid as a regular URI, please the {nameof(this.Type)} property first.");
/// <summary>
/// Represents a uri json converter.
/// </summary>
internal sealed class DiscordUriJsonConverter : JsonConverter
{
/// <summary>
/// Writes the json.
/// </summary>
/// <param name="writer">The writer.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The serializer.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => writer.WriteValue((value as DiscordUri)._value);
/// <summary>
/// Reads the json.
/// </summary>
/// <param name="reader">The reader.</param>
/// <param name="objectType">The object type.</param>
/// <param name="existingValue">The existing value.</param>
/// <param name="serializer">The serializer.</param>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
var val = reader.Value;
return val == null
? null
: val is not string s
? throw new JsonReaderException("DiscordUri value invalid format! This is a bug in DisCatSharp. " +
$"Include the type in your bug report: [[{reader.TokenType}]]")
: IsStandard(s)
? new DiscordUri(new Uri(s))
: new DiscordUri(s);
}
/// <summary>
/// Whether it can be converted.
/// </summary>
/// <param name="objectType">The object type.</param>
/// <returns>A bool.</returns>
public override bool CanConvert(Type objectType) => objectType == typeof(DiscordUri);
}
}
/// <summary>
/// Represents a uri type.
/// </summary>
public enum DiscordUriType : byte
{
/// <summary>
/// Represents a URI that conforms to RFC 3986, meaning it's stored internally as a <see cref="Uri"/> and will
/// contain a trailing slash after the domain name.
/// </summary>
Standard,
/// <summary>
/// Represents a URI that does not conform to RFC 3986, meaning it's stored internally as a plain string and
/// should be treated as one.
/// </summary>
NonStandard
}
diff --git a/DisCatSharp/Entities/Embed/DiscordEmbed.cs b/DisCatSharp/Entities/Embed/DiscordEmbed.cs
index 0fc1fd768..8ba86ae33 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-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a discord embed.
/// </summary>
public sealed class DiscordEmbed
{
/// <summary>
/// Gets the embed's title.
/// </summary>
[JsonProperty("title", NullValueHandling = NullValueHandling.Ignore)]
public string Title { get; internal set; }
/// <summary>
/// Gets the embed's type.
/// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
public string Type { get; internal set; }
/// <summary>
/// Gets the embed's description.
/// </summary>
[JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)]
public string Description { get; internal set; }
/// <summary>
/// Gets the embed's url.
/// </summary>
[JsonProperty("url", NullValueHandling = NullValueHandling.Ignore)]
public Uri Url { get; internal set; }
/// <summary>
/// Gets the embed's timestamp.
/// </summary>
[JsonProperty("timestamp", NullValueHandling = NullValueHandling.Ignore)]
public DateTimeOffset? Timestamp { get; internal set; }
/// <summary>
/// Gets the embed's color.
/// </summary>
[JsonIgnore]
public Optional<DiscordColor> Color
=> this._colorLazy.Value;
[JsonProperty("color", NullValueHandling = NullValueHandling.Include)]
internal Optional<int> ColorInternal;
[JsonIgnore]
private readonly Lazy<Optional<DiscordColor>> _colorLazy;
/// <summary>
/// Gets the embed's footer.
/// </summary>
[JsonProperty("footer", NullValueHandling = NullValueHandling.Ignore)]
public DiscordEmbedFooter Footer { get; internal set; }
/// <summary>
/// Gets the embed's image.
/// </summary>
[JsonProperty("image", NullValueHandling = NullValueHandling.Ignore)]
public DiscordEmbedImage Image { get; internal set; }
/// <summary>
/// Gets the embed's thumbnail.
/// </summary>
[JsonProperty("thumbnail", NullValueHandling = NullValueHandling.Ignore)]
public DiscordEmbedThumbnail Thumbnail { get; internal set; }
/// <summary>
/// Gets the embed's video.
/// </summary>
[JsonProperty("video", NullValueHandling = NullValueHandling.Ignore)]
public DiscordEmbedVideo Video { get; internal set; }
/// <summary>
/// Gets the embed's provider.
/// </summary>
[JsonProperty("provider", NullValueHandling = NullValueHandling.Ignore)]
public DiscordEmbedProvider Provider { get; internal set; }
/// <summary>
/// Gets the embed's author.
/// </summary>
[JsonProperty("author", NullValueHandling = NullValueHandling.Ignore)]
public DiscordEmbedAuthor Author { get; internal set; }
/// <summary>
/// Gets the embed's fields.
/// </summary>
[JsonProperty("fields", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<DiscordEmbedField> Fields { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordEmbed"/> class.
/// </summary>
internal DiscordEmbed()
{
this._colorLazy = new Lazy<Optional<DiscordColor>>(() => this.ColorInternal.Map<DiscordColor>(c => c));
}
}
diff --git a/DisCatSharp/Entities/Embed/DiscordEmbedAuthor.cs b/DisCatSharp/Entities/Embed/DiscordEmbedAuthor.cs
index 40bf28e82..79c300544 100644
--- a/DisCatSharp/Entities/Embed/DiscordEmbedAuthor.cs
+++ b/DisCatSharp/Entities/Embed/DiscordEmbedAuthor.cs
@@ -1,65 +1,65 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Gets the author of a discord embed.
/// </summary>
public sealed class DiscordEmbedAuthor
{
/// <summary>
/// Gets the name of the author.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; set; }
/// <summary>
/// Gets the url of the author.
/// </summary>
[JsonProperty("url", NullValueHandling = NullValueHandling.Ignore)]
public Uri Url { get; set; }
/// <summary>
/// Gets the url of the author's icon.
/// </summary>
[JsonProperty("icon_url", NullValueHandling = NullValueHandling.Ignore)]
public DiscordUri IconUrl { get; set; }
/// <summary>
/// Gets the proxied url of the author's icon.
/// </summary>
[JsonProperty("proxy_icon_url", NullValueHandling = NullValueHandling.Ignore)]
public DiscordUri ProxyIconUrl { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordEmbedAuthor"/> class.
/// </summary>
internal DiscordEmbedAuthor()
{ }
}
diff --git a/DisCatSharp/Entities/Embed/DiscordEmbedBuilder.cs b/DisCatSharp/Entities/Embed/DiscordEmbedBuilder.cs
index d4f404513..09e47fd8c 100644
--- a/DisCatSharp/Entities/Embed/DiscordEmbedBuilder.cs
+++ b/DisCatSharp/Entities/Embed/DiscordEmbedBuilder.cs
@@ -1,652 +1,652 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Constructs embeds.
/// </summary>
public sealed class DiscordEmbedBuilder
{
/// <summary>
/// Gets or sets the embed's title.
/// </summary>
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;
/// <summary>
/// Gets or sets the embed's description.
/// </summary>
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;
/// <summary>
/// Gets or sets the url for the embed's title.
/// </summary>
public string Url
{
get => this._url?.ToString();
set => this._url = string.IsNullOrEmpty(value) ? null : new Uri(value);
}
private Uri _url;
/// <summary>
/// Gets or sets the embed's color.
/// </summary>
public Optional<DiscordColor> Color { get; set; }
/// <summary>
/// Gets or sets the embed's timestamp.
/// </summary>
public DateTimeOffset? Timestamp { get; set; }
/// <summary>
/// Gets or sets the embed's image url.
/// </summary>
public string ImageUrl
{
get => this._imageUri?.ToString();
set => this._imageUri = string.IsNullOrEmpty(value) ? null : new DiscordUri(value);
}
private DiscordUri _imageUri;
/// <summary>
/// Gets or sets the embed's author.
/// </summary>
public EmbedAuthor Author { get; set; }
/// <summary>
/// Gets or sets the embed's footer.
/// </summary>
public EmbedFooter Footer { get; set; }
/// <summary>
/// Gets or sets the embed's thumbnail.
/// </summary>
public EmbedThumbnail Thumbnail { get; set; }
/// <summary>
/// Gets the embed's fields.
/// </summary>
public IReadOnlyList<DiscordEmbedField> Fields { get; }
private readonly List<DiscordEmbedField> _fields = new();
/// <summary>
/// Constructs a new empty embed builder.
/// </summary>
public DiscordEmbedBuilder()
{
this.Fields = new ReadOnlyCollection<DiscordEmbedField>(this._fields);
}
/// <summary>
/// Constructs a new embed builder using another embed as prototype.
/// </summary>
/// <param name="original">Embed to use as prototype.</param>
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);
}
/// <summary>
/// Sets the embed's title.
/// </summary>
/// <param name="title">Title to set.</param>
/// <returns>This embed builder.</returns>
public DiscordEmbedBuilder WithTitle(string title)
{
this.Title = title;
return this;
}
/// <summary>
/// Sets the embed's description.
/// </summary>
/// <param name="description">Description to set.</param>
/// <returns>This embed builder.</returns>
public DiscordEmbedBuilder WithDescription(string description)
{
this.Description = description;
return this;
}
/// <summary>
/// Sets the embed's title url.
/// </summary>
/// <param name="url">Title url to set.</param>
/// <returns>This embed builder.</returns>
public DiscordEmbedBuilder WithUrl(string url)
{
this.Url = url;
return this;
}
/// <summary>
/// Sets the embed's title url.
/// </summary>
/// <param name="url">Title url to set.</param>
/// <returns>This embed builder.</returns>
public DiscordEmbedBuilder WithUrl(Uri url)
{
this._url = url;
return this;
}
/// <summary>
/// Sets the embed's color.
/// </summary>
/// <param name="color">Embed color to set.</param>
/// <returns>This embed builder.</returns>
public DiscordEmbedBuilder WithColor(DiscordColor color)
{
this.Color = color;
return this;
}
/// <summary>
/// Sets the embed's timestamp.
/// </summary>
/// <param name="timestamp">Timestamp to set.</param>
/// <returns>This embed builder.</returns>
public DiscordEmbedBuilder WithTimestamp(DateTimeOffset? timestamp)
{
this.Timestamp = timestamp;
return this;
}
/// <summary>
/// Sets the embed's timestamp.
/// </summary>
/// <param name="timestamp">Timestamp to set.</param>
/// <returns>This embed builder.</returns>
public DiscordEmbedBuilder WithTimestamp(DateTime? timestamp)
{
this.Timestamp = timestamp == null ? null : new DateTimeOffset(timestamp.Value);
return this;
}
/// <summary>
/// Sets the embed's timestamp based on a snowflake.
/// </summary>
/// <param name="snowflake">Snowflake to calculate timestamp from.</param>
/// <returns>This embed builder.</returns>
public DiscordEmbedBuilder WithTimestamp(ulong snowflake)
{
this.Timestamp = new DateTimeOffset(2015, 1, 1, 0, 0, 0, TimeSpan.Zero).AddMilliseconds(snowflake >> 22);
return this;
}
/// <summary>
/// Sets the embed's image url.
/// </summary>
/// <param name="url">Image url to set.</param>
/// <returns>This embed builder.</returns>
public DiscordEmbedBuilder WithImageUrl(string url)
{
this.ImageUrl = url;
return this;
}
/// <summary>
/// Sets the embed's image url.
/// </summary>
/// <param name="url">Image url to set.</param>
/// <returns>This embed builder.</returns>
public DiscordEmbedBuilder WithImageUrl(Uri url)
{
this._imageUri = new DiscordUri(url);
return this;
}
/// <summary>
/// Sets the embed's thumbnail.
/// </summary>
/// <param name="url">Thumbnail url to set.</param>
/// <param name="height">The height of the thumbnail to set.</param>
/// <param name="width">The width of the thumbnail to set.</param>
/// <returns>This embed builder.</returns>
public DiscordEmbedBuilder WithThumbnail(string url, int height = 0, int width = 0)
{
this.Thumbnail = new EmbedThumbnail
{
Url = url,
Height = height,
Width = width
};
return this;
}
/// <summary>
/// Sets the embed's thumbnail.
/// </summary>
/// <param name="url">Thumbnail url to set.</param>
/// <param name="height">The height of the thumbnail to set.</param>
/// <param name="width">The width of the thumbnail to set.</param>
/// <returns>This embed builder.</returns>
public DiscordEmbedBuilder WithThumbnail(Uri url, int height = 0, int width = 0)
{
this.Thumbnail = new EmbedThumbnail
{
Uri = new DiscordUri(url),
Height = height,
Width = width
};
return this;
}
/// <summary>
/// Sets the embed's author.
/// </summary>
/// <param name="name">Author's name.</param>
/// <param name="url">Author's url.</param>
/// <param name="iconUrl">Author icon's url.</param>
/// <returns>This embed builder.</returns>
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;
}
/// <summary>
/// Sets the embed's footer.
/// </summary>
/// <param name="text">Footer's text.</param>
/// <param name="iconUrl">Footer icon's url.</param>
/// <returns>This embed builder.</returns>
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;
}
/// <summary>
/// Adds a field to this embed.
/// </summary>
/// <param name="name">Name of the field to add.</param>
/// <param name="value">Value of the field to add.</param>
/// <param name="inline">Whether the field is to be inline or not.</param>
/// <returns>This embed builder.</returns>
[Obsolete("DiscordEmbedFields should be constructed manually.")]
public DiscordEmbedBuilder AddField(string name, string value, bool inline = false)
=> this.AddField(new DiscordEmbedField(name, value, inline));
/// <summary>
/// Adds a field to this embed.
/// </summary>
/// <param name="field">The field to add.</param>
/// <returns>This embed builder.</returns>
public DiscordEmbedBuilder AddField(DiscordEmbedField field)
{
if (this._fields.Count >= 25)
throw new InvalidOperationException("Cannot add more than 25 fields.");
this._fields.Add(field);
return this;
}
/// <summary>
/// Adds multiple fields to this embed.
/// </summary>
/// <param name="fields">The fields to add.</param>
/// <returns>This embed builder.</returns>
public DiscordEmbedBuilder AddFields(params DiscordEmbedField[] fields)
=> this.AddFields((IEnumerable<DiscordEmbedField>)fields);
/// <summary>
/// Adds multiple fields to this embed.
/// </summary>
/// <param name="fields">The fields to add.</param>
/// <returns>This embed builder.</returns>
public DiscordEmbedBuilder AddFields(IEnumerable<DiscordEmbedField> fields)
=> fields.Aggregate(this, (x, y) => x.AddField(y));
/// <summary>
/// Removes a field from this embed, if it is part of it.
/// </summary>
/// <param name="field">The field to remove.</param>
/// <returns>This embed builder.</returns>
public DiscordEmbedBuilder RemoveField(DiscordEmbedField field)
{
this._fields.Remove(field);
return this;
}
/// <summary>
/// Removes multiple fields from this embed, if they are part of it.
/// </summary>
/// <param name="fields">The fields to remove.</param>
/// <returns>This embed builder.</returns>
public DiscordEmbedBuilder RemoveFields(params DiscordEmbedField[] fields)
{
this.RemoveFields((IEnumerable<DiscordEmbedField>)fields);
return this;
}
/// <summary>
/// Removes multiple fields from this embed, if they are part of it.
/// </summary>
/// <param name="fields">The fields to remove.</param>
/// <returns>This embed builder.</returns>
public DiscordEmbed RemoveFields(IEnumerable<DiscordEmbedField> fields)
{
this._fields.RemoveAll(x => fields.Contains(x));
return this;
}
/// <summary>
/// Removes a field of the specified index from this embed.
/// </summary>
/// <param name="index">Index of the field to remove.</param>
/// <returns>This embed builder.</returns>
public DiscordEmbedBuilder RemoveFieldAt(int index)
{
this._fields.RemoveAt(index);
return this;
}
/// <summary>
/// Removes fields of the specified range from this embed.
/// </summary>
/// <param name="index">Index of the first field to remove.</param>
/// <param name="count">Number of fields to remove.</param>
/// <returns>This embed builder.</returns>
public DiscordEmbedBuilder RemoveFieldRange(int index, int count)
{
this._fields.RemoveRange(index, count);
return this;
}
/// <summary>
/// Removes all fields from this embed.
/// </summary>
/// <returns>This embed builder.</returns>
public DiscordEmbedBuilder ClearFields()
{
this._fields.Clear();
return this;
}
/// <summary>
/// Constructs a new embed from data supplied to this builder.
/// </summary>
/// <returns>New discord embed.</returns>
public DiscordEmbed Build()
{
var embed = new DiscordEmbed
{
Title = this._title,
Description = this._description,
Url = this._url,
ColorInternal = this.Color.Map(e => e.Value),
Timestamp = this.Timestamp
};
if (this.Footer != null)
embed.Footer = new DiscordEmbedFooter
{
Text = this.Footer.Text,
IconUrl = this.Footer.IconUri
};
if (this.Author != null)
embed.Author = new DiscordEmbedAuthor
{
Name = this.Author.Name,
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,
Height = this.Thumbnail.Height,
Width = this.Thumbnail.Width
};
embed.Fields = new ReadOnlyCollection<DiscordEmbedField>(new List<DiscordEmbedField>(this._fields)); // copy the list, don't wrap it, prevents mutation
var charCount = 0;
if (embed.Fields.Any())
{
foreach (var field in embed.Fields)
{
charCount += field.Name.Length;
charCount += field.Value.Length;
}
}
if (embed.Author != null && !string.IsNullOrEmpty(embed.Author.Name))
charCount += embed.Author.Name.Length;
if (embed.Footer != null && !string.IsNullOrEmpty(embed.Footer.Text))
charCount += embed.Footer.Text.Length;
if (!string.IsNullOrEmpty(embed.Title))
charCount += embed.Title.Length;
if (!string.IsNullOrEmpty(embed.Description))
charCount += embed.Description.Length;
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;
}
/// <summary>
/// Implicitly converts this builder to an embed.
/// </summary>
/// <param name="builder">Builder to convert.</param>
public static implicit operator DiscordEmbed(DiscordEmbedBuilder builder)
=> builder?.Build();
/// <summary>
/// Represents an embed author.
/// </summary>
public class EmbedAuthor
{
/// <summary>
/// Gets or sets the name of the author.
/// </summary>
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;
/// <summary>
/// Gets or sets the Url to which the author's link leads.
/// </summary>
public string Url
{
get => this.Uri?.ToString();
set => this.Uri = string.IsNullOrEmpty(value) ? null : new Uri(value);
}
internal Uri Uri;
/// <summary>
/// Gets or sets the Author's icon url.
/// </summary>
public string IconUrl
{
get => this.IconUri?.ToString();
set => this.IconUri = string.IsNullOrEmpty(value) ? null : new DiscordUri(value);
}
internal DiscordUri IconUri;
}
/// <summary>
/// Represents an embed footer.
/// </summary>
public class EmbedFooter
{
/// <summary>
/// Gets or sets the text of the footer.
/// </summary>
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;
/// <summary>
/// Gets or sets the Url
/// </summary>
public string IconUrl
{
get => this.IconUri?.ToString();
set => this.IconUri = string.IsNullOrEmpty(value) ? null : new DiscordUri(value);
}
internal DiscordUri IconUri;
}
/// <summary>
/// Represents an embed thumbnail.
/// </summary>
public class EmbedThumbnail
{
/// <summary>
/// Gets or sets the thumbnail's image url.
/// </summary>
public string Url
{
get => this.Uri?.ToString();
set => this.Uri = string.IsNullOrEmpty(value) ? null : new DiscordUri(value);
}
internal DiscordUri Uri;
/// <summary>
/// Gets or sets the thumbnail's height.
/// </summary>
public int Height
{
get => this._height;
set => this._height = value >= 0 ? value : 0;
}
private int _height;
/// <summary>
/// Gets or sets the thumbnail's width.
/// </summary>
public int Width
{
get => this._width;
set => this._width = value >= 0 ? value : 0;
}
private int _width;
}
}
diff --git a/DisCatSharp/Entities/Embed/DiscordEmbedField.cs b/DisCatSharp/Entities/Embed/DiscordEmbedField.cs
index ed2b90448..74a3c3576 100644
--- a/DisCatSharp/Entities/Embed/DiscordEmbedField.cs
+++ b/DisCatSharp/Entities/Embed/DiscordEmbedField.cs
@@ -1,104 +1,104 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Entities;
/// <summary>
/// Represents a field inside a discord embed.
/// </summary>
public sealed class DiscordEmbedField
{
private string _name;
/// <summary>
/// The name of the field.
/// Must be non-null, non-empty and &lt;= 256 characters.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name
{
get => this._name;
set
{
if (string.IsNullOrWhiteSpace(value))
{
if (value == null)
throw new ArgumentNullException(nameof(value));
throw new ArgumentException("Name cannot be empty or whitespace.", nameof(value));
}
if (value.Length > 256)
throw new ArgumentException("Embed field name length cannot exceed 256 characters.");
this._name = value;
}
}
private string _value;
/// <summary>
/// The value of the field.
/// Must be non-null, non-empty and &lt;= 1024 characters.
/// </summary>
[JsonProperty("value", NullValueHandling = NullValueHandling.Ignore)]
public string Value
{
get => this._value;
set
{
if (string.IsNullOrWhiteSpace(value))
{
if (value == null)
throw new ArgumentNullException(nameof(value));
throw new ArgumentException("Value cannot be empty or whitespace.", nameof(value));
}
if (value.Length > 1024)
throw new ArgumentException("Embed field value length cannot exceed 1024 characters.");
this._value = value;
}
}
/// <summary>
/// Whether or not this field should display inline.
/// </summary>
[JsonProperty("inline", NullValueHandling = NullValueHandling.Ignore)]
public bool Inline { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordEmbedField"/> class.
/// </summary>
/// <param name="name"><see cref="Name"/></param>
/// <param name="value"><see cref="Value"/></param>
/// <param name="inline"><see cref="Inline"/></param>
public DiscordEmbedField(string name, string value, bool inline = false)
{
this.Name = name;
this.Value = value;
this.Inline = inline;
}
}
diff --git a/DisCatSharp/Entities/Embed/DiscordEmbedFooter.cs b/DisCatSharp/Entities/Embed/DiscordEmbedFooter.cs
index 399a357f3..cab21ac3c 100644
--- a/DisCatSharp/Entities/Embed/DiscordEmbedFooter.cs
+++ b/DisCatSharp/Entities/Embed/DiscordEmbedFooter.cs
@@ -1,57 +1,57 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a footer in an embed.
/// </summary>
public sealed class DiscordEmbedFooter
{
/// <summary>
/// Gets the footer's text.
/// </summary>
[JsonProperty("text", NullValueHandling = NullValueHandling.Ignore)]
public string Text { get; internal set; }
/// <summary>
/// Gets the url of the footer's icon.
/// </summary>
[JsonProperty("icon_url", NullValueHandling = NullValueHandling.Ignore)]
public DiscordUri IconUrl { get; internal set; }
/// <summary>
/// Gets the proxied url of the footer's icon.
/// </summary>
[JsonProperty("proxy_icon_url", NullValueHandling = NullValueHandling.Ignore)]
public DiscordUri ProxyIconUrl { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordEmbedFooter"/> class.
/// </summary>
internal DiscordEmbedFooter()
{ }
}
diff --git a/DisCatSharp/Entities/Embed/DiscordEmbedImage.cs b/DisCatSharp/Entities/Embed/DiscordEmbedImage.cs
index cbb0663cc..33c1e2ff5 100644
--- a/DisCatSharp/Entities/Embed/DiscordEmbedImage.cs
+++ b/DisCatSharp/Entities/Embed/DiscordEmbedImage.cs
@@ -1,63 +1,63 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents an image in an embed.
/// </summary>
public sealed class DiscordEmbedImage
{
/// <summary>
/// Gets the source url of the image.
/// </summary>
[JsonProperty("url", NullValueHandling = NullValueHandling.Ignore)]
public DiscordUri Url { get; internal set; }
/// <summary>
/// Gets a proxied url of the image.
/// </summary>
[JsonProperty("proxy_url", NullValueHandling = NullValueHandling.Ignore)]
public DiscordUri ProxyUrl { get; internal set; }
/// <summary>
/// Gets the height of the image.
/// </summary>
[JsonProperty("height", NullValueHandling = NullValueHandling.Ignore)]
public int Height { get; internal set; }
/// <summary>
/// Gets the width of the image.
/// </summary>
[JsonProperty("width", NullValueHandling = NullValueHandling.Ignore)]
public int Width { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordEmbedImage"/> class.
/// </summary>
internal DiscordEmbedImage()
{ }
}
diff --git a/DisCatSharp/Entities/Embed/DiscordEmbedProvider.cs b/DisCatSharp/Entities/Embed/DiscordEmbedProvider.cs
index b99b2a06d..b4e81d610 100644
--- a/DisCatSharp/Entities/Embed/DiscordEmbedProvider.cs
+++ b/DisCatSharp/Entities/Embed/DiscordEmbedProvider.cs
@@ -1,51 +1,51 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Entities;
/// <summary>
/// Represents an embed provider.
/// </summary>
public sealed class DiscordEmbedProvider
{
/// <summary>
/// Gets the name of the provider.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; internal set; }
/// <summary>
/// Gets the url of the provider.
/// </summary>
[JsonProperty("url", NullValueHandling = NullValueHandling.Ignore)]
public Uri Url { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordEmbedProvider"/> class.
/// </summary>
internal DiscordEmbedProvider()
{ }
}
diff --git a/DisCatSharp/Entities/Embed/DiscordEmbedThumbnail.cs b/DisCatSharp/Entities/Embed/DiscordEmbedThumbnail.cs
index eb350af29..c927bb536 100644
--- a/DisCatSharp/Entities/Embed/DiscordEmbedThumbnail.cs
+++ b/DisCatSharp/Entities/Embed/DiscordEmbedThumbnail.cs
@@ -1,63 +1,63 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a thumbnail in an embed.
/// </summary>
public sealed class DiscordEmbedThumbnail
{
/// <summary>
/// Gets the source url of the thumbnail (only https).
/// </summary>
[JsonProperty("url", NullValueHandling = NullValueHandling.Ignore)]
public DiscordUri Url { get; internal set; }
/// <summary>
/// Gets a proxied url of the thumbnail.
/// </summary>
[JsonProperty("proxy_url", NullValueHandling = NullValueHandling.Ignore)]
public DiscordUri ProxyUrl { get; internal set; }
/// <summary>
/// Gets the height of the thumbnail.
/// </summary>
[JsonProperty("height", NullValueHandling = NullValueHandling.Ignore)]
public int Height { get; internal set; }
/// <summary>
/// Gets the width of the thumbnail.
/// </summary>
[JsonProperty("width", NullValueHandling = NullValueHandling.Ignore)]
public int Width { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordEmbedThumbnail"/> class.
/// </summary>
internal DiscordEmbedThumbnail()
{ }
}
diff --git a/DisCatSharp/Entities/Embed/DiscordEmbedVideo.cs b/DisCatSharp/Entities/Embed/DiscordEmbedVideo.cs
index c902fb1a3..85b4e4c63 100644
--- a/DisCatSharp/Entities/Embed/DiscordEmbedVideo.cs
+++ b/DisCatSharp/Entities/Embed/DiscordEmbedVideo.cs
@@ -1,57 +1,57 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Entities;
/// <summary>
/// Represents a video inside an embed.
/// </summary>
public sealed class DiscordEmbedVideo
{
/// <summary>
/// Gets the source url of the video.
/// </summary>
[JsonProperty("url", NullValueHandling = NullValueHandling.Ignore)]
public Uri Url { get; internal set; }
/// <summary>
/// Gets the height of the video.
/// </summary>
[JsonProperty("height", NullValueHandling = NullValueHandling.Ignore)]
public int Height { get; internal set; }
/// <summary>
/// Gets the width of the video.
/// </summary>
[JsonProperty("width", NullValueHandling = NullValueHandling.Ignore)]
public int Width { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordEmbedVideo"/> class.
/// </summary>
internal DiscordEmbedVideo()
{ }
}
diff --git a/DisCatSharp/Entities/Emoji/DiscordEmoji.EmojiUtils.cs b/DisCatSharp/Entities/Emoji/DiscordEmoji.EmojiUtils.cs
index 2e30f76d0..a7266415c 100644
--- a/DisCatSharp/Entities/Emoji/DiscordEmoji.EmojiUtils.cs
+++ b/DisCatSharp/Entities/Emoji/DiscordEmoji.EmojiUtils.cs
@@ -1,10148 +1,10148 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents discord emoji.
/// </summary>
public partial class DiscordEmoji
{
/// <summary>
/// Gets a mapping of :name: => unicode.
/// </summary>
private static IReadOnlyDictionary<string, string> s_unicodeEmojis { get; }
/// <summary>
/// Gets a mapping of unicode => :name:.
/// </summary>
private static IReadOnlyDictionary<string, string> s_discordNameLookup { get; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordEmoji"/> class.
/// </summary>
static DiscordEmoji()
{
#region Generated Emoji Map
s_unicodeEmojis = new Dictionary<string, string>
{
[":100:"] = "\U0001f4af",
[":1234:"] = "\U0001f522",
[":8ball:"] = "\U0001f3b1",
[":a:"] = "\U0001f170\ufe0f",
[":ab:"] = "\U0001f18e",
[":abacus:"] = "\U0001f9ee",
[":abc:"] = "\U0001f524",
[":abcd:"] = "\U0001f521",
[":accept:"] = "\U0001f251",
[":accordion:"] = "\U0001fa97",
[":adhesive_bandage:"] = "\U0001fa79",
[":adult:"] = "\U0001f9d1",
[":adult_tone1:"] = "\U0001f9d1\U0001f3fb",
[":adult_light_skin_tone:"] = "\U0001f9d1\U0001f3fb",
[":adult::skin-tone-1:"] = "\U0001f9d1\U0001f3fb",
[":adult_tone2:"] = "\U0001f9d1\U0001f3fc",
[":adult_medium_light_skin_tone:"] = "\U0001f9d1\U0001f3fc",
[":adult::skin-tone-2:"] = "\U0001f9d1\U0001f3fc",
[":adult_tone3:"] = "\U0001f9d1\U0001f3fd",
[":adult_medium_skin_tone:"] = "\U0001f9d1\U0001f3fd",
[":adult::skin-tone-3:"] = "\U0001f9d1\U0001f3fd",
[":adult_tone4:"] = "\U0001f9d1\U0001f3fe",
[":adult_medium_dark_skin_tone:"] = "\U0001f9d1\U0001f3fe",
[":adult::skin-tone-4:"] = "\U0001f9d1\U0001f3fe",
[":adult_tone5:"] = "\U0001f9d1\U0001f3ff",
[":adult_dark_skin_tone:"] = "\U0001f9d1\U0001f3ff",
[":adult::skin-tone-5:"] = "\U0001f9d1\U0001f3ff",
[":aerial_tramway:"] = "\U0001f6a1",
[":airplane:"] = "\u2708\ufe0f",
[":airplane_arriving:"] = "\U0001f6ec",
[":airplane_departure:"] = "\U0001f6eb",
[":airplane_small:"] = "\U0001f6e9\ufe0f",
[":small_airplane:"] = "\U0001f6e9\ufe0f",
[":alarm_clock:"] = "\u23f0",
[":alembic:"] = "\u2697\ufe0f",
[":alien:"] = "\U0001f47d",
[":ambulance:"] = "\U0001f691",
[":amphora:"] = "\U0001f3fa",
[":anatomical_heart:"] = "\U0001fac0",
[":anchor:"] = "\u2693",
[":angel:"] = "\U0001f47c",
[":angel_tone1:"] = "\U0001f47c\U0001f3fb",
[":angel::skin-tone-1:"] = "\U0001f47c\U0001f3fb",
[":angel_tone2:"] = "\U0001f47c\U0001f3fc",
[":angel::skin-tone-2:"] = "\U0001f47c\U0001f3fc",
[":angel_tone3:"] = "\U0001f47c\U0001f3fd",
[":angel::skin-tone-3:"] = "\U0001f47c\U0001f3fd",
[":angel_tone4:"] = "\U0001f47c\U0001f3fe",
[":angel::skin-tone-4:"] = "\U0001f47c\U0001f3fe",
[":angel_tone5:"] = "\U0001f47c\U0001f3ff",
[":angel::skin-tone-5:"] = "\U0001f47c\U0001f3ff",
[":anger:"] = "\U0001f4a2",
[":anger_right:"] = "\U0001f5ef\ufe0f",
[":right_anger_bubble:"] = "\U0001f5ef\ufe0f",
[":angry:"] = "\U0001f620",
[">:("] = "\U0001f620",
[">:-("] = "\U0001f620",
[">=("] = "\U0001f620",
[">=-("] = "\U0001f620",
[":anguished:"] = "\U0001f627",
[":ant:"] = "\U0001f41c",
[":apple:"] = "\U0001f34e",
[":aquarius:"] = "\u2652",
[":aries:"] = "\u2648",
[":arrow_backward:"] = "\u25c0\ufe0f",
[":arrow_double_down:"] = "\u23ec",
[":arrow_double_up:"] = "\u23eb",
[":arrow_down:"] = "\u2b07\ufe0f",
[":arrow_down_small:"] = "\U0001f53d",
[":arrow_forward:"] = "\u25b6\ufe0f",
[":arrow_heading_down:"] = "\u2935\ufe0f",
[":arrow_heading_up:"] = "\u2934\ufe0f",
[":arrow_left:"] = "\u2b05\ufe0f",
[":arrow_lower_left:"] = "\u2199\ufe0f",
[":arrow_lower_right:"] = "\u2198\ufe0f",
[":arrow_right:"] = "\u27a1\ufe0f",
[":arrow_right_hook:"] = "\u21aa\ufe0f",
[":arrow_up:"] = "\u2b06\ufe0f",
[":arrow_up_down:"] = "\u2195\ufe0f",
[":arrow_up_small:"] = "\U0001f53c",
[":arrow_upper_left:"] = "\u2196\ufe0f",
[":arrow_upper_right:"] = "\u2197\ufe0f",
[":arrows_clockwise:"] = "\U0001f503",
[":arrows_counterclockwise:"] = "\U0001f504",
[":art:"] = "\U0001f3a8",
[":articulated_lorry:"] = "\U0001f69b",
[":artist:"] = "\U0001f9d1\u200d\U0001f3a8",
[":artist_tone1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f3a8",
[":artist_light_skin_tone:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f3a8",
[":artist::skin-tone-1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f3a8",
[":artist_tone2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f3a8",
[":artist_medium_light_skin_tone:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f3a8",
[":artist::skin-tone-2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f3a8",
[":artist_tone3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f3a8",
[":artist_medium_skin_tone:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f3a8",
[":artist::skin-tone-3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f3a8",
[":artist_tone4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f3a8",
[":artist_medium_dark_skin_tone:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f3a8",
[":artist::skin-tone-4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f3a8",
[":artist_tone5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f3a8",
[":artist_dark_skin_tone:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f3a8",
[":artist::skin-tone-5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f3a8",
[":asterisk:"] = "\u002a\ufe0f\u20e3",
[":keycap_asterisk:"] = "\u002a\ufe0f\u20e3",
[":astonished:"] = "\U0001f632",
[":astronaut:"] = "\U0001f9d1\u200d\U0001f680",
[":astronaut_tone1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f680",
[":astronaut_light_skin_tone:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f680",
[":astronaut::skin-tone-1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f680",
[":astronaut_tone2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f680",
[":astronaut_medium_light_skin_tone:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f680",
[":astronaut::skin-tone-2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f680",
[":astronaut_tone3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f680",
[":astronaut_medium_skin_tone:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f680",
[":astronaut::skin-tone-3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f680",
[":astronaut_tone4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f680",
[":astronaut_medium_dark_skin_tone:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f680",
[":astronaut::skin-tone-4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f680",
[":astronaut_tone5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f680",
[":astronaut_dark_skin_tone:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f680",
[":astronaut::skin-tone-5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f680",
[":athletic_shoe:"] = "\U0001f45f",
[":atm:"] = "\U0001f3e7",
[":atom:"] = "\u269b\ufe0f",
[":atom_symbol:"] = "\u269b\ufe0f",
[":auto_rickshaw:"] = "\U0001f6fa",
[":avocado:"] = "\U0001f951",
[":axe:"] = "\U0001fa93",
[":b:"] = "\U0001f171\ufe0f",
[":baby:"] = "\U0001f476",
[":baby_bottle:"] = "\U0001f37c",
[":baby_chick:"] = "\U0001f424",
[":baby_symbol:"] = "\U0001f6bc",
[":baby_tone1:"] = "\U0001f476\U0001f3fb",
[":baby::skin-tone-1:"] = "\U0001f476\U0001f3fb",
[":baby_tone2:"] = "\U0001f476\U0001f3fc",
[":baby::skin-tone-2:"] = "\U0001f476\U0001f3fc",
[":baby_tone3:"] = "\U0001f476\U0001f3fd",
[":baby::skin-tone-3:"] = "\U0001f476\U0001f3fd",
[":baby_tone4:"] = "\U0001f476\U0001f3fe",
[":baby::skin-tone-4:"] = "\U0001f476\U0001f3fe",
[":baby_tone5:"] = "\U0001f476\U0001f3ff",
[":baby::skin-tone-5:"] = "\U0001f476\U0001f3ff",
[":back:"] = "\U0001f519",
[":bacon:"] = "\U0001f953",
[":badger:"] = "\U0001f9a1",
[":badminton:"] = "\U0001f3f8",
[":bagel:"] = "\U0001f96f",
[":baggage_claim:"] = "\U0001f6c4",
[":ballet_shoes:"] = "\U0001fa70",
[":balloon:"] = "\U0001f388",
[":ballot_box:"] = "\U0001f5f3\ufe0f",
[":ballot_box_with_ballot:"] = "\U0001f5f3\ufe0f",
[":ballot_box_with_check:"] = "\u2611\ufe0f",
[":bamboo:"] = "\U0001f38d",
[":banana:"] = "\U0001f34c",
[":bangbang:"] = "\u203c\ufe0f",
[":banjo:"] = "\U0001fa95",
[":bank:"] = "\U0001f3e6",
[":bar_chart:"] = "\U0001f4ca",
[":barber:"] = "\U0001f488",
[":baseball:"] = "\u26be",
[":basket:"] = "\U0001f9fa",
[":basketball:"] = "\U0001f3c0",
[":bat:"] = "\U0001f987",
[":bath:"] = "\U0001f6c0",
[":bath_tone1:"] = "\U0001f6c0\U0001f3fb",
[":bath::skin-tone-1:"] = "\U0001f6c0\U0001f3fb",
[":bath_tone2:"] = "\U0001f6c0\U0001f3fc",
[":bath::skin-tone-2:"] = "\U0001f6c0\U0001f3fc",
[":bath_tone3:"] = "\U0001f6c0\U0001f3fd",
[":bath::skin-tone-3:"] = "\U0001f6c0\U0001f3fd",
[":bath_tone4:"] = "\U0001f6c0\U0001f3fe",
[":bath::skin-tone-4:"] = "\U0001f6c0\U0001f3fe",
[":bath_tone5:"] = "\U0001f6c0\U0001f3ff",
[":bath::skin-tone-5:"] = "\U0001f6c0\U0001f3ff",
[":bathtub:"] = "\U0001f6c1",
[":battery:"] = "\U0001f50b",
[":beach:"] = "\U0001f3d6\ufe0f",
[":beach_with_umbrella:"] = "\U0001f3d6\ufe0f",
[":beach_umbrella:"] = "\u26f1\ufe0f",
[":umbrella_on_ground:"] = "\u26f1\ufe0f",
[":bear:"] = "\U0001f43b",
[":bearded_person:"] = "\U0001f9d4",
[":bearded_person_tone1:"] = "\U0001f9d4\U0001f3fb",
[":bearded_person_light_skin_tone:"] = "\U0001f9d4\U0001f3fb",
[":bearded_person::skin-tone-1:"] = "\U0001f9d4\U0001f3fb",
[":bearded_person_tone2:"] = "\U0001f9d4\U0001f3fc",
[":bearded_person_medium_light_skin_tone:"] = "\U0001f9d4\U0001f3fc",
[":bearded_person::skin-tone-2:"] = "\U0001f9d4\U0001f3fc",
[":bearded_person_tone3:"] = "\U0001f9d4\U0001f3fd",
[":bearded_person_medium_skin_tone:"] = "\U0001f9d4\U0001f3fd",
[":bearded_person::skin-tone-3:"] = "\U0001f9d4\U0001f3fd",
[":bearded_person_tone4:"] = "\U0001f9d4\U0001f3fe",
[":bearded_person_medium_dark_skin_tone:"] = "\U0001f9d4\U0001f3fe",
[":bearded_person::skin-tone-4:"] = "\U0001f9d4\U0001f3fe",
[":bearded_person_tone5:"] = "\U0001f9d4\U0001f3ff",
[":bearded_person_dark_skin_tone:"] = "\U0001f9d4\U0001f3ff",
[":bearded_person::skin-tone-5:"] = "\U0001f9d4\U0001f3ff",
[":beaver:"] = "\U0001f9ab",
[":bed:"] = "\U0001f6cf\ufe0f",
[":bee:"] = "\U0001f41d",
[":beer:"] = "\U0001f37a",
[":beers:"] = "\U0001f37b",
[":beetle:"] = "\U0001fab2",
[":beginner:"] = "\U0001f530",
[":bell:"] = "\U0001f514",
[":bell_pepper:"] = "\U0001fad1",
[":bellhop:"] = "\U0001f6ce\ufe0f",
[":bellhop_bell:"] = "\U0001f6ce\ufe0f",
[":bento:"] = "\U0001f371",
[":beverage_box:"] = "\U0001f9c3",
[":bike:"] = "\U0001f6b2",
[":bikini:"] = "\U0001f459",
[":billed_cap:"] = "\U0001f9e2",
[":biohazard:"] = "\u2623\ufe0f",
[":biohazard_sign:"] = "\u2623\ufe0f",
[":bird:"] = "\U0001f426",
[":birthday:"] = "\U0001f382",
[":bison:"] = "\U0001f9ac",
[":black_cat:"] = "\U0001f408\u200d\u2b1b",
[":black_circle:"] = "\u26ab",
[":black_heart:"] = "\U0001f5a4",
[":black_joker:"] = "\U0001f0cf",
[":black_large_square:"] = "\u2b1b",
[":black_medium_small_square:"] = "\u25fe",
[":black_medium_square:"] = "\u25fc\ufe0f",
[":black_nib:"] = "\u2712\ufe0f",
[":black_small_square:"] = "\u25aa\ufe0f",
[":black_square_button:"] = "\U0001f532",
[":blond_haired_man:"] = "\U0001f471\u200d\u2642\ufe0f",
[":blond_haired_man_tone1:"] = "\U0001f471\U0001f3fb\u200d\u2642\ufe0f",
[":blond_haired_man_light_skin_tone:"] = "\U0001f471\U0001f3fb\u200d\u2642\ufe0f",
[":blond_haired_man::skin-tone-1:"] = "\U0001f471\U0001f3fb\u200d\u2642\ufe0f",
[":blond_haired_man_tone2:"] = "\U0001f471\U0001f3fc\u200d\u2642\ufe0f",
[":blond_haired_man_medium_light_skin_tone:"] = "\U0001f471\U0001f3fc\u200d\u2642\ufe0f",
[":blond_haired_man::skin-tone-2:"] = "\U0001f471\U0001f3fc\u200d\u2642\ufe0f",
[":blond_haired_man_tone3:"] = "\U0001f471\U0001f3fd\u200d\u2642\ufe0f",
[":blond_haired_man_medium_skin_tone:"] = "\U0001f471\U0001f3fd\u200d\u2642\ufe0f",
[":blond_haired_man::skin-tone-3:"] = "\U0001f471\U0001f3fd\u200d\u2642\ufe0f",
[":blond_haired_man_tone4:"] = "\U0001f471\U0001f3fe\u200d\u2642\ufe0f",
[":blond_haired_man_medium_dark_skin_tone:"] = "\U0001f471\U0001f3fe\u200d\u2642\ufe0f",
[":blond_haired_man::skin-tone-4:"] = "\U0001f471\U0001f3fe\u200d\u2642\ufe0f",
[":blond_haired_man_tone5:"] = "\U0001f471\U0001f3ff\u200d\u2642\ufe0f",
[":blond_haired_man_dark_skin_tone:"] = "\U0001f471\U0001f3ff\u200d\u2642\ufe0f",
[":blond_haired_man::skin-tone-5:"] = "\U0001f471\U0001f3ff\u200d\u2642\ufe0f",
[":blond_haired_person:"] = "\U0001f471",
[":person_with_blond_hair:"] = "\U0001f471",
[":blond_haired_person_tone1:"] = "\U0001f471\U0001f3fb",
[":person_with_blond_hair_tone1:"] = "\U0001f471\U0001f3fb",
[":blond_haired_person::skin-tone-1:"] = "\U0001f471\U0001f3fb",
[":person_with_blond_hair::skin-tone-1:"] = "\U0001f471\U0001f3fb",
[":blond_haired_person_tone2:"] = "\U0001f471\U0001f3fc",
[":person_with_blond_hair_tone2:"] = "\U0001f471\U0001f3fc",
[":blond_haired_person::skin-tone-2:"] = "\U0001f471\U0001f3fc",
[":person_with_blond_hair::skin-tone-2:"] = "\U0001f471\U0001f3fc",
[":blond_haired_person_tone3:"] = "\U0001f471\U0001f3fd",
[":person_with_blond_hair_tone3:"] = "\U0001f471\U0001f3fd",
[":blond_haired_person::skin-tone-3:"] = "\U0001f471\U0001f3fd",
[":person_with_blond_hair::skin-tone-3:"] = "\U0001f471\U0001f3fd",
[":blond_haired_person_tone4:"] = "\U0001f471\U0001f3fe",
[":person_with_blond_hair_tone4:"] = "\U0001f471\U0001f3fe",
[":blond_haired_person::skin-tone-4:"] = "\U0001f471\U0001f3fe",
[":person_with_blond_hair::skin-tone-4:"] = "\U0001f471\U0001f3fe",
[":blond_haired_person_tone5:"] = "\U0001f471\U0001f3ff",
[":person_with_blond_hair_tone5:"] = "\U0001f471\U0001f3ff",
[":blond_haired_person::skin-tone-5:"] = "\U0001f471\U0001f3ff",
[":person_with_blond_hair::skin-tone-5:"] = "\U0001f471\U0001f3ff",
[":blond_haired_woman:"] = "\U0001f471\u200d\u2640\ufe0f",
[":blond_haired_woman_tone1:"] = "\U0001f471\U0001f3fb\u200d\u2640\ufe0f",
[":blond_haired_woman_light_skin_tone:"] = "\U0001f471\U0001f3fb\u200d\u2640\ufe0f",
[":blond_haired_woman::skin-tone-1:"] = "\U0001f471\U0001f3fb\u200d\u2640\ufe0f",
[":blond_haired_woman_tone2:"] = "\U0001f471\U0001f3fc\u200d\u2640\ufe0f",
[":blond_haired_woman_medium_light_skin_tone:"] = "\U0001f471\U0001f3fc\u200d\u2640\ufe0f",
[":blond_haired_woman::skin-tone-2:"] = "\U0001f471\U0001f3fc\u200d\u2640\ufe0f",
[":blond_haired_woman_tone3:"] = "\U0001f471\U0001f3fd\u200d\u2640\ufe0f",
[":blond_haired_woman_medium_skin_tone:"] = "\U0001f471\U0001f3fd\u200d\u2640\ufe0f",
[":blond_haired_woman::skin-tone-3:"] = "\U0001f471\U0001f3fd\u200d\u2640\ufe0f",
[":blond_haired_woman_tone4:"] = "\U0001f471\U0001f3fe\u200d\u2640\ufe0f",
[":blond_haired_woman_medium_dark_skin_tone:"] = "\U0001f471\U0001f3fe\u200d\u2640\ufe0f",
[":blond_haired_woman::skin-tone-4:"] = "\U0001f471\U0001f3fe\u200d\u2640\ufe0f",
[":blond_haired_woman_tone5:"] = "\U0001f471\U0001f3ff\u200d\u2640\ufe0f",
[":blond_haired_woman_dark_skin_tone:"] = "\U0001f471\U0001f3ff\u200d\u2640\ufe0f",
[":blond_haired_woman::skin-tone-5:"] = "\U0001f471\U0001f3ff\u200d\u2640\ufe0f",
[":blossom:"] = "\U0001f33c",
[":blowfish:"] = "\U0001f421",
[":blue_book:"] = "\U0001f4d8",
[":blue_car:"] = "\U0001f699",
[":blue_circle:"] = "\U0001f535",
[":blue_heart:"] = "\U0001f499",
[":blue_square:"] = "\U0001f7e6",
[":blueberries:"] = "\U0001fad0",
[":blush:"] = "\U0001f60a",
[":\")"] = "\U0001f60a",
[":-\")"] = "\U0001f60a",
["=\")"] = "\U0001f60a",
["=-\")"] = "\U0001f60a",
[":boar:"] = "\U0001f417",
[":bomb:"] = "\U0001f4a3",
[":bone:"] = "\U0001f9b4",
[":book:"] = "\U0001f4d6",
[":bookmark:"] = "\U0001f516",
[":bookmark_tabs:"] = "\U0001f4d1",
[":books:"] = "\U0001f4da",
[":boom:"] = "\U0001f4a5",
[":boomerang:"] = "\U0001fa83",
[":boot:"] = "\U0001f462",
[":bouquet:"] = "\U0001f490",
[":bow_and_arrow:"] = "\U0001f3f9",
[":archery:"] = "\U0001f3f9",
[":bowl_with_spoon:"] = "\U0001f963",
[":bowling:"] = "\U0001f3b3",
[":boxing_glove:"] = "\U0001f94a",
[":boxing_gloves:"] = "\U0001f94a",
[":boy:"] = "\U0001f466",
[":boy_tone1:"] = "\U0001f466\U0001f3fb",
[":boy::skin-tone-1:"] = "\U0001f466\U0001f3fb",
[":boy_tone2:"] = "\U0001f466\U0001f3fc",
[":boy::skin-tone-2:"] = "\U0001f466\U0001f3fc",
[":boy_tone3:"] = "\U0001f466\U0001f3fd",
[":boy::skin-tone-3:"] = "\U0001f466\U0001f3fd",
[":boy_tone4:"] = "\U0001f466\U0001f3fe",
[":boy::skin-tone-4:"] = "\U0001f466\U0001f3fe",
[":boy_tone5:"] = "\U0001f466\U0001f3ff",
[":boy::skin-tone-5:"] = "\U0001f466\U0001f3ff",
[":brain:"] = "\U0001f9e0",
[":bread:"] = "\U0001f35e",
[":breast_feeding:"] = "\U0001f931",
[":breast_feeding_tone1:"] = "\U0001f931\U0001f3fb",
[":breast_feeding_light_skin_tone:"] = "\U0001f931\U0001f3fb",
[":breast_feeding::skin-tone-1:"] = "\U0001f931\U0001f3fb",
[":breast_feeding_tone2:"] = "\U0001f931\U0001f3fc",
[":breast_feeding_medium_light_skin_tone:"] = "\U0001f931\U0001f3fc",
[":breast_feeding::skin-tone-2:"] = "\U0001f931\U0001f3fc",
[":breast_feeding_tone3:"] = "\U0001f931\U0001f3fd",
[":breast_feeding_medium_skin_tone:"] = "\U0001f931\U0001f3fd",
[":breast_feeding::skin-tone-3:"] = "\U0001f931\U0001f3fd",
[":breast_feeding_tone4:"] = "\U0001f931\U0001f3fe",
[":breast_feeding_medium_dark_skin_tone:"] = "\U0001f931\U0001f3fe",
[":breast_feeding::skin-tone-4:"] = "\U0001f931\U0001f3fe",
[":breast_feeding_tone5:"] = "\U0001f931\U0001f3ff",
[":breast_feeding_dark_skin_tone:"] = "\U0001f931\U0001f3ff",
[":breast_feeding::skin-tone-5:"] = "\U0001f931\U0001f3ff",
[":bricks:"] = "\U0001f9f1",
[":bridge_at_night:"] = "\U0001f309",
[":briefcase:"] = "\U0001f4bc",
[":briefs:"] = "\U0001fa72",
[":broccoli:"] = "\U0001f966",
[":broken_heart:"] = "\U0001f494",
["</3"] = "\U0001f494",
["<\\3"] = "\U0001f494",
[":broom:"] = "\U0001f9f9",
[":brown_circle:"] = "\U0001f7e4",
[":brown_heart:"] = "\U0001f90e",
[":brown_square:"] = "\U0001f7eb",
[":bubble_tea:"] = "\U0001f9cb",
[":bucket:"] = "\U0001faa3",
[":bug:"] = "\U0001f41b",
[":bulb:"] = "\U0001f4a1",
[":bullettrain_front:"] = "\U0001f685",
[":bullettrain_side:"] = "\U0001f684",
[":burrito:"] = "\U0001f32f",
[":bus:"] = "\U0001f68c",
[":busstop:"] = "\U0001f68f",
[":bust_in_silhouette:"] = "\U0001f464",
[":busts_in_silhouette:"] = "\U0001f465",
[":butter:"] = "\U0001f9c8",
[":butterfly:"] = "\U0001f98b",
[":cactus:"] = "\U0001f335",
[":cake:"] = "\U0001f370",
[":calendar:"] = "\U0001f4c6",
[":calendar_spiral:"] = "\U0001f5d3\ufe0f",
[":spiral_calendar_pad:"] = "\U0001f5d3\ufe0f",
[":call_me:"] = "\U0001f919",
[":call_me_hand:"] = "\U0001f919",
[":call_me_tone1:"] = "\U0001f919\U0001f3fb",
[":call_me_hand_tone1:"] = "\U0001f919\U0001f3fb",
[":call_me::skin-tone-1:"] = "\U0001f919\U0001f3fb",
[":call_me_hand::skin-tone-1:"] = "\U0001f919\U0001f3fb",
[":call_me_tone2:"] = "\U0001f919\U0001f3fc",
[":call_me_hand_tone2:"] = "\U0001f919\U0001f3fc",
[":call_me::skin-tone-2:"] = "\U0001f919\U0001f3fc",
[":call_me_hand::skin-tone-2:"] = "\U0001f919\U0001f3fc",
[":call_me_tone3:"] = "\U0001f919\U0001f3fd",
[":call_me_hand_tone3:"] = "\U0001f919\U0001f3fd",
[":call_me::skin-tone-3:"] = "\U0001f919\U0001f3fd",
[":call_me_hand::skin-tone-3:"] = "\U0001f919\U0001f3fd",
[":call_me_tone4:"] = "\U0001f919\U0001f3fe",
[":call_me_hand_tone4:"] = "\U0001f919\U0001f3fe",
[":call_me::skin-tone-4:"] = "\U0001f919\U0001f3fe",
[":call_me_hand::skin-tone-4:"] = "\U0001f919\U0001f3fe",
[":call_me_tone5:"] = "\U0001f919\U0001f3ff",
[":call_me_hand_tone5:"] = "\U0001f919\U0001f3ff",
[":call_me::skin-tone-5:"] = "\U0001f919\U0001f3ff",
[":call_me_hand::skin-tone-5:"] = "\U0001f919\U0001f3ff",
[":calling:"] = "\U0001f4f2",
[":camel:"] = "\U0001f42b",
[":camera:"] = "\U0001f4f7",
[":camera_with_flash:"] = "\U0001f4f8",
[":camping:"] = "\U0001f3d5\ufe0f",
[":cancer:"] = "\u264b",
[":candle:"] = "\U0001f56f\ufe0f",
[":candy:"] = "\U0001f36c",
[":canned_food:"] = "\U0001f96b",
[":canoe:"] = "\U0001f6f6",
[":kayak:"] = "\U0001f6f6",
[":capital_abcd:"] = "\U0001f520",
[":capricorn:"] = "\u2651",
[":card_box:"] = "\U0001f5c3\ufe0f",
[":card_file_box:"] = "\U0001f5c3\ufe0f",
[":card_index:"] = "\U0001f4c7",
[":carousel_horse:"] = "\U0001f3a0",
[":carpentry_saw:"] = "\U0001fa9a",
[":carrot:"] = "\U0001f955",
[":cat:"] = "\U0001f431",
[":cat2:"] = "\U0001f408",
[":cd:"] = "\U0001f4bf",
[":chains:"] = "\u26d3\ufe0f",
[":chair:"] = "\U0001fa91",
[":champagne:"] = "\U0001f37e",
[":bottle_with_popping_cork:"] = "\U0001f37e",
[":champagne_glass:"] = "\U0001f942",
[":clinking_glass:"] = "\U0001f942",
[":chart:"] = "\U0001f4b9",
[":chart_with_downwards_trend:"] = "\U0001f4c9",
[":chart_with_upwards_trend:"] = "\U0001f4c8",
[":checkered_flag:"] = "\U0001f3c1",
[":cheese:"] = "\U0001f9c0",
[":cheese_wedge:"] = "\U0001f9c0",
[":cherries:"] = "\U0001f352",
[":cherry_blossom:"] = "\U0001f338",
[":chess_pawn:"] = "\u265f\ufe0f",
[":chestnut:"] = "\U0001f330",
[":chicken:"] = "\U0001f414",
[":child:"] = "\U0001f9d2",
[":child_tone1:"] = "\U0001f9d2\U0001f3fb",
[":child_light_skin_tone:"] = "\U0001f9d2\U0001f3fb",
[":child::skin-tone-1:"] = "\U0001f9d2\U0001f3fb",
[":child_tone2:"] = "\U0001f9d2\U0001f3fc",
[":child_medium_light_skin_tone:"] = "\U0001f9d2\U0001f3fc",
[":child::skin-tone-2:"] = "\U0001f9d2\U0001f3fc",
[":child_tone3:"] = "\U0001f9d2\U0001f3fd",
[":child_medium_skin_tone:"] = "\U0001f9d2\U0001f3fd",
[":child::skin-tone-3:"] = "\U0001f9d2\U0001f3fd",
[":child_tone4:"] = "\U0001f9d2\U0001f3fe",
[":child_medium_dark_skin_tone:"] = "\U0001f9d2\U0001f3fe",
[":child::skin-tone-4:"] = "\U0001f9d2\U0001f3fe",
[":child_tone5:"] = "\U0001f9d2\U0001f3ff",
[":child_dark_skin_tone:"] = "\U0001f9d2\U0001f3ff",
[":child::skin-tone-5:"] = "\U0001f9d2\U0001f3ff",
[":children_crossing:"] = "\U0001f6b8",
[":chipmunk:"] = "\U0001f43f\ufe0f",
[":chocolate_bar:"] = "\U0001f36b",
[":chopsticks:"] = "\U0001f962",
[":christmas_tree:"] = "\U0001f384",
[":church:"] = "\u26ea",
[":cinema:"] = "\U0001f3a6",
[":circus_tent:"] = "\U0001f3aa",
[":city_dusk:"] = "\U0001f306",
[":city_sunset:"] = "\U0001f307",
[":city_sunrise:"] = "\U0001f307",
[":cityscape:"] = "\U0001f3d9\ufe0f",
[":cl:"] = "\U0001f191",
[":clap:"] = "\U0001f44f",
[":clap_tone1:"] = "\U0001f44f\U0001f3fb",
[":clap::skin-tone-1:"] = "\U0001f44f\U0001f3fb",
[":clap_tone2:"] = "\U0001f44f\U0001f3fc",
[":clap::skin-tone-2:"] = "\U0001f44f\U0001f3fc",
[":clap_tone3:"] = "\U0001f44f\U0001f3fd",
[":clap::skin-tone-3:"] = "\U0001f44f\U0001f3fd",
[":clap_tone4:"] = "\U0001f44f\U0001f3fe",
[":clap::skin-tone-4:"] = "\U0001f44f\U0001f3fe",
[":clap_tone5:"] = "\U0001f44f\U0001f3ff",
[":clap::skin-tone-5:"] = "\U0001f44f\U0001f3ff",
[":clapper:"] = "\U0001f3ac",
[":classical_building:"] = "\U0001f3db\ufe0f",
[":clipboard:"] = "\U0001f4cb",
[":clock:"] = "\U0001f570\ufe0f",
[":mantlepiece_clock:"] = "\U0001f570\ufe0f",
[":clock1:"] = "\U0001f550",
[":clock10:"] = "\U0001f559",
[":clock1030:"] = "\U0001f565",
[":clock11:"] = "\U0001f55a",
[":clock1130:"] = "\U0001f566",
[":clock12:"] = "\U0001f55b",
[":clock1230:"] = "\U0001f567",
[":clock130:"] = "\U0001f55c",
[":clock2:"] = "\U0001f551",
[":clock230:"] = "\U0001f55d",
[":clock3:"] = "\U0001f552",
[":clock330:"] = "\U0001f55e",
[":clock4:"] = "\U0001f553",
[":clock430:"] = "\U0001f55f",
[":clock5:"] = "\U0001f554",
[":clock530:"] = "\U0001f560",
[":clock6:"] = "\U0001f555",
[":clock630:"] = "\U0001f561",
[":clock7:"] = "\U0001f556",
[":clock730:"] = "\U0001f562",
[":clock8:"] = "\U0001f557",
[":clock830:"] = "\U0001f563",
[":clock9:"] = "\U0001f558",
[":clock930:"] = "\U0001f564",
[":closed_book:"] = "\U0001f4d5",
[":closed_lock_with_key:"] = "\U0001f510",
[":closed_umbrella:"] = "\U0001f302",
[":cloud:"] = "\u2601\ufe0f",
[":cloud_lightning:"] = "\U0001f329\ufe0f",
[":cloud_with_lightning:"] = "\U0001f329\ufe0f",
[":cloud_rain:"] = "\U0001f327\ufe0f",
[":cloud_with_rain:"] = "\U0001f327\ufe0f",
[":cloud_snow:"] = "\U0001f328\ufe0f",
[":cloud_with_snow:"] = "\U0001f328\ufe0f",
[":cloud_tornado:"] = "\U0001f32a\ufe0f",
[":cloud_with_tornado:"] = "\U0001f32a\ufe0f",
[":clown:"] = "\U0001f921",
[":clown_face:"] = "\U0001f921",
[":clubs:"] = "\u2663\ufe0f",
[":coat:"] = "\U0001f9e5",
[":cockroach:"] = "\U0001fab3",
[":cocktail:"] = "\U0001f378",
[":coconut:"] = "\U0001f965",
[":coffee:"] = "\u2615",
[":coffin:"] = "\u26b0\ufe0f",
[":coin:"] = "\U0001fa99",
[":cold_face:"] = "\U0001f976",
[":cold_sweat:"] = "\U0001f630",
[":comet:"] = "\u2604\ufe0f",
[":compass:"] = "\U0001f9ed",
[":compression:"] = "\U0001f5dc\ufe0f",
[":computer:"] = "\U0001f4bb",
[":confetti_ball:"] = "\U0001f38a",
[":confounded:"] = "\U0001f616",
[":confused:"] = "\U0001f615",
[":-\\"] = "\U0001f615",
[":-/"] = "\U0001f615",
["=-\\"] = "\U0001f615",
["=-/"] = "\U0001f615",
[":congratulations:"] = "\u3297\ufe0f",
[":construction:"] = "\U0001f6a7",
[":construction_site:"] = "\U0001f3d7\ufe0f",
[":building_construction:"] = "\U0001f3d7\ufe0f",
[":construction_worker:"] = "\U0001f477",
[":construction_worker_tone1:"] = "\U0001f477\U0001f3fb",
[":construction_worker::skin-tone-1:"] = "\U0001f477\U0001f3fb",
[":construction_worker_tone2:"] = "\U0001f477\U0001f3fc",
[":construction_worker::skin-tone-2:"] = "\U0001f477\U0001f3fc",
[":construction_worker_tone3:"] = "\U0001f477\U0001f3fd",
[":construction_worker::skin-tone-3:"] = "\U0001f477\U0001f3fd",
[":construction_worker_tone4:"] = "\U0001f477\U0001f3fe",
[":construction_worker::skin-tone-4:"] = "\U0001f477\U0001f3fe",
[":construction_worker_tone5:"] = "\U0001f477\U0001f3ff",
[":construction_worker::skin-tone-5:"] = "\U0001f477\U0001f3ff",
[":control_knobs:"] = "\U0001f39b\ufe0f",
[":convenience_store:"] = "\U0001f3ea",
[":cook:"] = "\U0001f9d1\u200d\U0001f373",
[":cook_tone1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f373",
[":cook_light_skin_tone:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f373",
[":cook::skin-tone-1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f373",
[":cook_tone2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f373",
[":cook_medium_light_skin_tone:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f373",
[":cook::skin-tone-2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f373",
[":cook_tone3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f373",
[":cook_medium_skin_tone:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f373",
[":cook::skin-tone-3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f373",
[":cook_tone4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f373",
[":cook_medium_dark_skin_tone:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f373",
[":cook::skin-tone-4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f373",
[":cook_tone5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f373",
[":cook_dark_skin_tone:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f373",
[":cook::skin-tone-5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f373",
[":cookie:"] = "\U0001f36a",
[":cooking:"] = "\U0001f373",
[":cool:"] = "\U0001f192",
[":copyright:"] = "\u00a9\ufe0f",
[":corn:"] = "\U0001f33d",
[":couch:"] = "\U0001f6cb\ufe0f",
[":couch_and_lamp:"] = "\U0001f6cb\ufe0f",
[":couple:"] = "\U0001f46b",
[":couple_mm:"] = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f468",
[":couple_with_heart_mm:"] = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f468",
[":couple_with_heart:"] = "\U0001f491",
[":couple_with_heart_woman_man:"] = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f468",
[":couple_ww:"] = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f469",
[":couple_with_heart_ww:"] = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f469",
[":couplekiss:"] = "\U0001f48f",
[":cow:"] = "\U0001f42e",
[":cow2:"] = "\U0001f404",
[":cowboy:"] = "\U0001f920",
[":face_with_cowboy_hat:"] = "\U0001f920",
[":crab:"] = "\U0001f980",
[":crayon:"] = "\U0001f58d\ufe0f",
[":lower_left_crayon:"] = "\U0001f58d\ufe0f",
[":credit_card:"] = "\U0001f4b3",
[":crescent_moon:"] = "\U0001f319",
[":cricket:"] = "\U0001f997",
[":cricket_game:"] = "\U0001f3cf",
[":cricket_bat_ball:"] = "\U0001f3cf",
[":crocodile:"] = "\U0001f40a",
[":croissant:"] = "\U0001f950",
[":cross:"] = "\u271d\ufe0f",
[":latin_cross:"] = "\u271d\ufe0f",
[":crossed_flags:"] = "\U0001f38c",
[":crossed_swords:"] = "\u2694\ufe0f",
[":crown:"] = "\U0001f451",
[":cruise_ship:"] = "\U0001f6f3\ufe0f",
[":passenger_ship:"] = "\U0001f6f3\ufe0f",
[":cry:"] = "\U0001f622",
[":'("] = "\U0001f622",
[":'-("] = "\U0001f622",
[":,("] = "\U0001f622",
[":,-("] = "\U0001f622",
["='("] = "\U0001f622",
["='-("] = "\U0001f622",
["=,("] = "\U0001f622",
["=,-("] = "\U0001f622",
[":crying_cat_face:"] = "\U0001f63f",
[":crystal_ball:"] = "\U0001f52e",
[":cucumber:"] = "\U0001f952",
[":cup_with_straw:"] = "\U0001f964",
[":cupcake:"] = "\U0001f9c1",
[":cupid:"] = "\U0001f498",
[":curling_stone:"] = "\U0001f94c",
[":curly_loop:"] = "\u27b0",
[":currency_exchange:"] = "\U0001f4b1",
[":curry:"] = "\U0001f35b",
[":custard:"] = "\U0001f36e",
[":pudding:"] = "\U0001f36e",
[":flan:"] = "\U0001f36e",
[":customs:"] = "\U0001f6c3",
[":cut_of_meat:"] = "\U0001f969",
[":cyclone:"] = "\U0001f300",
[":dagger:"] = "\U0001f5e1\ufe0f",
[":dagger_knife:"] = "\U0001f5e1\ufe0f",
[":dancer:"] = "\U0001f483",
[":dancer_tone1:"] = "\U0001f483\U0001f3fb",
[":dancer::skin-tone-1:"] = "\U0001f483\U0001f3fb",
[":dancer_tone2:"] = "\U0001f483\U0001f3fc",
[":dancer::skin-tone-2:"] = "\U0001f483\U0001f3fc",
[":dancer_tone3:"] = "\U0001f483\U0001f3fd",
[":dancer::skin-tone-3:"] = "\U0001f483\U0001f3fd",
[":dancer_tone4:"] = "\U0001f483\U0001f3fe",
[":dancer::skin-tone-4:"] = "\U0001f483\U0001f3fe",
[":dancer_tone5:"] = "\U0001f483\U0001f3ff",
[":dancer::skin-tone-5:"] = "\U0001f483\U0001f3ff",
[":dango:"] = "\U0001f361",
[":dark_sunglasses:"] = "\U0001f576\ufe0f",
[":dart:"] = "\U0001f3af",
[":dash:"] = "\U0001f4a8",
[":date:"] = "\U0001f4c5",
[":deaf_man:"] = "\U0001f9cf\u200d\u2642\ufe0f",
[":deaf_man_tone1:"] = "\U0001f9cf\U0001f3fb\u200d\u2642\ufe0f",
[":deaf_man_light_skin_tone:"] = "\U0001f9cf\U0001f3fb\u200d\u2642\ufe0f",
[":deaf_man::skin-tone-1:"] = "\U0001f9cf\U0001f3fb\u200d\u2642\ufe0f",
[":deaf_man_tone2:"] = "\U0001f9cf\U0001f3fc\u200d\u2642\ufe0f",
[":deaf_man_medium_light_skin_tone:"] = "\U0001f9cf\U0001f3fc\u200d\u2642\ufe0f",
[":deaf_man::skin-tone-2:"] = "\U0001f9cf\U0001f3fc\u200d\u2642\ufe0f",
[":deaf_man_tone3:"] = "\U0001f9cf\U0001f3fd\u200d\u2642\ufe0f",
[":deaf_man_medium_skin_tone:"] = "\U0001f9cf\U0001f3fd\u200d\u2642\ufe0f",
[":deaf_man::skin-tone-3:"] = "\U0001f9cf\U0001f3fd\u200d\u2642\ufe0f",
[":deaf_man_tone4:"] = "\U0001f9cf\U0001f3fe\u200d\u2642\ufe0f",
[":deaf_man_medium_dark_skin_tone:"] = "\U0001f9cf\U0001f3fe\u200d\u2642\ufe0f",
[":deaf_man::skin-tone-4:"] = "\U0001f9cf\U0001f3fe\u200d\u2642\ufe0f",
[":deaf_man_tone5:"] = "\U0001f9cf\U0001f3ff\u200d\u2642\ufe0f",
[":deaf_man_dark_skin_tone:"] = "\U0001f9cf\U0001f3ff\u200d\u2642\ufe0f",
[":deaf_man::skin-tone-5:"] = "\U0001f9cf\U0001f3ff\u200d\u2642\ufe0f",
[":deaf_person:"] = "\U0001f9cf",
[":deaf_person_tone1:"] = "\U0001f9cf\U0001f3fb",
[":deaf_person_light_skin_tone:"] = "\U0001f9cf\U0001f3fb",
[":deaf_person::skin-tone-1:"] = "\U0001f9cf\U0001f3fb",
[":deaf_person_tone2:"] = "\U0001f9cf\U0001f3fc",
[":deaf_person_medium_light_skin_tone:"] = "\U0001f9cf\U0001f3fc",
[":deaf_person::skin-tone-2:"] = "\U0001f9cf\U0001f3fc",
[":deaf_person_tone3:"] = "\U0001f9cf\U0001f3fd",
[":deaf_person_medium_skin_tone:"] = "\U0001f9cf\U0001f3fd",
[":deaf_person::skin-tone-3:"] = "\U0001f9cf\U0001f3fd",
[":deaf_person_tone4:"] = "\U0001f9cf\U0001f3fe",
[":deaf_person_medium_dark_skin_tone:"] = "\U0001f9cf\U0001f3fe",
[":deaf_person::skin-tone-4:"] = "\U0001f9cf\U0001f3fe",
[":deaf_person_tone5:"] = "\U0001f9cf\U0001f3ff",
[":deaf_person_dark_skin_tone:"] = "\U0001f9cf\U0001f3ff",
[":deaf_person::skin-tone-5:"] = "\U0001f9cf\U0001f3ff",
[":deaf_woman:"] = "\U0001f9cf\u200d\u2640\ufe0f",
[":deaf_woman_tone1:"] = "\U0001f9cf\U0001f3fb\u200d\u2640\ufe0f",
[":deaf_woman_light_skin_tone:"] = "\U0001f9cf\U0001f3fb\u200d\u2640\ufe0f",
[":deaf_woman::skin-tone-1:"] = "\U0001f9cf\U0001f3fb\u200d\u2640\ufe0f",
[":deaf_woman_tone2:"] = "\U0001f9cf\U0001f3fc\u200d\u2640\ufe0f",
[":deaf_woman_medium_light_skin_tone:"] = "\U0001f9cf\U0001f3fc\u200d\u2640\ufe0f",
[":deaf_woman::skin-tone-2:"] = "\U0001f9cf\U0001f3fc\u200d\u2640\ufe0f",
[":deaf_woman_tone3:"] = "\U0001f9cf\U0001f3fd\u200d\u2640\ufe0f",
[":deaf_woman_medium_skin_tone:"] = "\U0001f9cf\U0001f3fd\u200d\u2640\ufe0f",
[":deaf_woman::skin-tone-3:"] = "\U0001f9cf\U0001f3fd\u200d\u2640\ufe0f",
[":deaf_woman_tone4:"] = "\U0001f9cf\U0001f3fe\u200d\u2640\ufe0f",
[":deaf_woman_medium_dark_skin_tone:"] = "\U0001f9cf\U0001f3fe\u200d\u2640\ufe0f",
[":deaf_woman::skin-tone-4:"] = "\U0001f9cf\U0001f3fe\u200d\u2640\ufe0f",
[":deaf_woman_tone5:"] = "\U0001f9cf\U0001f3ff\u200d\u2640\ufe0f",
[":deaf_woman_dark_skin_tone:"] = "\U0001f9cf\U0001f3ff\u200d\u2640\ufe0f",
[":deaf_woman::skin-tone-5:"] = "\U0001f9cf\U0001f3ff\u200d\u2640\ufe0f",
[":deciduous_tree:"] = "\U0001f333",
[":deer:"] = "\U0001f98c",
[":department_store:"] = "\U0001f3ec",
[":desert:"] = "\U0001f3dc\ufe0f",
[":desktop:"] = "\U0001f5a5\ufe0f",
[":desktop_computer:"] = "\U0001f5a5\ufe0f",
[":detective:"] = "\U0001f575\ufe0f",
[":spy:"] = "\U0001f575\ufe0f",
[":sleuth_or_spy:"] = "\U0001f575\ufe0f",
[":detective_tone1:"] = "\U0001f575\U0001f3fb",
[":spy_tone1:"] = "\U0001f575\U0001f3fb",
[":sleuth_or_spy_tone1:"] = "\U0001f575\U0001f3fb",
[":detective::skin-tone-1:"] = "\U0001f575\U0001f3fb",
[":spy::skin-tone-1:"] = "\U0001f575\U0001f3fb",
[":sleuth_or_spy::skin-tone-1:"] = "\U0001f575\U0001f3fb",
[":detective_tone2:"] = "\U0001f575\U0001f3fc",
[":spy_tone2:"] = "\U0001f575\U0001f3fc",
[":sleuth_or_spy_tone2:"] = "\U0001f575\U0001f3fc",
[":detective::skin-tone-2:"] = "\U0001f575\U0001f3fc",
[":spy::skin-tone-2:"] = "\U0001f575\U0001f3fc",
[":sleuth_or_spy::skin-tone-2:"] = "\U0001f575\U0001f3fc",
[":detective_tone3:"] = "\U0001f575\U0001f3fd",
[":spy_tone3:"] = "\U0001f575\U0001f3fd",
[":sleuth_or_spy_tone3:"] = "\U0001f575\U0001f3fd",
[":detective::skin-tone-3:"] = "\U0001f575\U0001f3fd",
[":spy::skin-tone-3:"] = "\U0001f575\U0001f3fd",
[":sleuth_or_spy::skin-tone-3:"] = "\U0001f575\U0001f3fd",
[":detective_tone4:"] = "\U0001f575\U0001f3fe",
[":spy_tone4:"] = "\U0001f575\U0001f3fe",
[":sleuth_or_spy_tone4:"] = "\U0001f575\U0001f3fe",
[":detective::skin-tone-4:"] = "\U0001f575\U0001f3fe",
[":spy::skin-tone-4:"] = "\U0001f575\U0001f3fe",
[":sleuth_or_spy::skin-tone-4:"] = "\U0001f575\U0001f3fe",
[":detective_tone5:"] = "\U0001f575\U0001f3ff",
[":spy_tone5:"] = "\U0001f575\U0001f3ff",
[":sleuth_or_spy_tone5:"] = "\U0001f575\U0001f3ff",
[":detective::skin-tone-5:"] = "\U0001f575\U0001f3ff",
[":spy::skin-tone-5:"] = "\U0001f575\U0001f3ff",
[":sleuth_or_spy::skin-tone-5:"] = "\U0001f575\U0001f3ff",
[":diamond_shape_with_a_dot_inside:"] = "\U0001f4a0",
[":diamonds:"] = "\u2666\ufe0f",
[":disappointed:"] = "\U0001f61e",
[":disappointed_relieved:"] = "\U0001f625",
[":disguised_face:"] = "\U0001f978",
[":dividers:"] = "\U0001f5c2\ufe0f",
[":card_index_dividers:"] = "\U0001f5c2\ufe0f",
[":diving_mask:"] = "\U0001f93f",
[":diya_lamp:"] = "\U0001fa94",
[":dizzy:"] = "\U0001f4ab",
[":dizzy_face:"] = "\U0001f635",
[":dna:"] = "\U0001f9ec",
[":do_not_litter:"] = "\U0001f6af",
[":dodo:"] = "\U0001f9a4",
[":dog:"] = "\U0001f436",
[":dog2:"] = "\U0001f415",
[":dollar:"] = "\U0001f4b5",
[":dolls:"] = "\U0001f38e",
[":dolphin:"] = "\U0001f42c",
[":door:"] = "\U0001f6aa",
[":doughnut:"] = "\U0001f369",
[":dove:"] = "\U0001f54a\ufe0f",
[":dove_of_peace:"] = "\U0001f54a\ufe0f",
[":dragon:"] = "\U0001f409",
[":dragon_face:"] = "\U0001f432",
[":dress:"] = "\U0001f457",
[":dromedary_camel:"] = "\U0001f42a",
[":drooling_face:"] = "\U0001f924",
[":drool:"] = "\U0001f924",
[":drop_of_blood:"] = "\U0001fa78",
[":droplet:"] = "\U0001f4a7",
[":drum:"] = "\U0001f941",
[":drum_with_drumsticks:"] = "\U0001f941",
[":duck:"] = "\U0001f986",
[":dumpling:"] = "\U0001f95f",
[":dvd:"] = "\U0001f4c0",
[":e_mail:"] = "\U0001f4e7",
[":email:"] = "\U0001f4e7",
[":eagle:"] = "\U0001f985",
[":ear:"] = "\U0001f442",
[":ear_of_rice:"] = "\U0001f33e",
[":ear_tone1:"] = "\U0001f442\U0001f3fb",
[":ear::skin-tone-1:"] = "\U0001f442\U0001f3fb",
[":ear_tone2:"] = "\U0001f442\U0001f3fc",
[":ear::skin-tone-2:"] = "\U0001f442\U0001f3fc",
[":ear_tone3:"] = "\U0001f442\U0001f3fd",
[":ear::skin-tone-3:"] = "\U0001f442\U0001f3fd",
[":ear_tone4:"] = "\U0001f442\U0001f3fe",
[":ear::skin-tone-4:"] = "\U0001f442\U0001f3fe",
[":ear_tone5:"] = "\U0001f442\U0001f3ff",
[":ear::skin-tone-5:"] = "\U0001f442\U0001f3ff",
[":ear_with_hearing_aid:"] = "\U0001f9bb",
[":ear_with_hearing_aid_tone1:"] = "\U0001f9bb\U0001f3fb",
[":ear_with_hearing_aid_light_skin_tone:"] = "\U0001f9bb\U0001f3fb",
[":ear_with_hearing_aid::skin-tone-1:"] = "\U0001f9bb\U0001f3fb",
[":ear_with_hearing_aid_tone2:"] = "\U0001f9bb\U0001f3fc",
[":ear_with_hearing_aid_medium_light_skin_tone:"] = "\U0001f9bb\U0001f3fc",
[":ear_with_hearing_aid::skin-tone-2:"] = "\U0001f9bb\U0001f3fc",
[":ear_with_hearing_aid_tone3:"] = "\U0001f9bb\U0001f3fd",
[":ear_with_hearing_aid_medium_skin_tone:"] = "\U0001f9bb\U0001f3fd",
[":ear_with_hearing_aid::skin-tone-3:"] = "\U0001f9bb\U0001f3fd",
[":ear_with_hearing_aid_tone4:"] = "\U0001f9bb\U0001f3fe",
[":ear_with_hearing_aid_medium_dark_skin_tone:"] = "\U0001f9bb\U0001f3fe",
[":ear_with_hearing_aid::skin-tone-4:"] = "\U0001f9bb\U0001f3fe",
[":ear_with_hearing_aid_tone5:"] = "\U0001f9bb\U0001f3ff",
[":ear_with_hearing_aid_dark_skin_tone:"] = "\U0001f9bb\U0001f3ff",
[":ear_with_hearing_aid::skin-tone-5:"] = "\U0001f9bb\U0001f3ff",
[":earth_africa:"] = "\U0001f30d",
[":earth_americas:"] = "\U0001f30e",
[":earth_asia:"] = "\U0001f30f",
[":egg:"] = "\U0001f95a",
[":eggplant:"] = "\U0001f346",
[":eight:"] = "\u0038\ufe0f\u20e3",
[":eight_pointed_black_star:"] = "\u2734\ufe0f",
[":eight_spoked_asterisk:"] = "\u2733\ufe0f",
[":eject:"] = "\u23cf\ufe0f",
[":eject_symbol:"] = "\u23cf\ufe0f",
[":electric_plug:"] = "\U0001f50c",
[":elephant:"] = "\U0001f418",
[":elevator:"] = "\U0001f6d7",
[":elf:"] = "\U0001f9dd",
[":elf_tone1:"] = "\U0001f9dd\U0001f3fb",
[":elf_light_skin_tone:"] = "\U0001f9dd\U0001f3fb",
[":elf::skin-tone-1:"] = "\U0001f9dd\U0001f3fb",
[":elf_tone2:"] = "\U0001f9dd\U0001f3fc",
[":elf_medium_light_skin_tone:"] = "\U0001f9dd\U0001f3fc",
[":elf::skin-tone-2:"] = "\U0001f9dd\U0001f3fc",
[":elf_tone3:"] = "\U0001f9dd\U0001f3fd",
[":elf_medium_skin_tone:"] = "\U0001f9dd\U0001f3fd",
[":elf::skin-tone-3:"] = "\U0001f9dd\U0001f3fd",
[":elf_tone4:"] = "\U0001f9dd\U0001f3fe",
[":elf_medium_dark_skin_tone:"] = "\U0001f9dd\U0001f3fe",
[":elf::skin-tone-4:"] = "\U0001f9dd\U0001f3fe",
[":elf_tone5:"] = "\U0001f9dd\U0001f3ff",
[":elf_dark_skin_tone:"] = "\U0001f9dd\U0001f3ff",
[":elf::skin-tone-5:"] = "\U0001f9dd\U0001f3ff",
[":end:"] = "\U0001f51a",
[":england:"] = "\U0001f3f4\U000e0067\U000e0062\U000e0065\U000e006e\U000e0067\U000e007f",
[":envelope:"] = "\u2709\ufe0f",
[":envelope_with_arrow:"] = "\U0001f4e9",
[":euro:"] = "\U0001f4b6",
[":european_castle:"] = "\U0001f3f0",
[":european_post_office:"] = "\U0001f3e4",
[":evergreen_tree:"] = "\U0001f332",
[":exclamation:"] = "\u2757",
[":exploding_head:"] = "\U0001f92f",
[":expressionless:"] = "\U0001f611",
[":eye:"] = "\U0001f441\ufe0f",
[":eye_in_speech_bubble:"] = "\U0001f441\u200d\U0001f5e8",
[":eyeglasses:"] = "\U0001f453",
[":eyes:"] = "\U0001f440",
[":face_vomiting:"] = "\U0001f92e",
[":face_with_hand_over_mouth:"] = "\U0001f92d",
[":face_with_monocle:"] = "\U0001f9d0",
[":face_with_raised_eyebrow:"] = "\U0001f928",
[":face_with_symbols_over_mouth:"] = "\U0001f92c",
[":factory:"] = "\U0001f3ed",
[":factory_worker:"] = "\U0001f9d1\u200d\U0001f3ed",
[":factory_worker_tone1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f3ed",
[":factory_worker_light_skin_tone:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f3ed",
[":factory_worker::skin-tone-1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f3ed",
[":factory_worker_tone2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f3ed",
[":factory_worker_medium_light_skin_tone:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f3ed",
[":factory_worker::skin-tone-2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f3ed",
[":factory_worker_tone3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f3ed",
[":factory_worker_medium_skin_tone:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f3ed",
[":factory_worker::skin-tone-3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f3ed",
[":factory_worker_tone4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f3ed",
[":factory_worker_medium_dark_skin_tone:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f3ed",
[":factory_worker::skin-tone-4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f3ed",
[":factory_worker_tone5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f3ed",
[":factory_worker_dark_skin_tone:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f3ed",
[":factory_worker::skin-tone-5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f3ed",
[":fairy:"] = "\U0001f9da",
[":fairy_tone1:"] = "\U0001f9da\U0001f3fb",
[":fairy_light_skin_tone:"] = "\U0001f9da\U0001f3fb",
[":fairy::skin-tone-1:"] = "\U0001f9da\U0001f3fb",
[":fairy_tone2:"] = "\U0001f9da\U0001f3fc",
[":fairy_medium_light_skin_tone:"] = "\U0001f9da\U0001f3fc",
[":fairy::skin-tone-2:"] = "\U0001f9da\U0001f3fc",
[":fairy_tone3:"] = "\U0001f9da\U0001f3fd",
[":fairy_medium_skin_tone:"] = "\U0001f9da\U0001f3fd",
[":fairy::skin-tone-3:"] = "\U0001f9da\U0001f3fd",
[":fairy_tone4:"] = "\U0001f9da\U0001f3fe",
[":fairy_medium_dark_skin_tone:"] = "\U0001f9da\U0001f3fe",
[":fairy::skin-tone-4:"] = "\U0001f9da\U0001f3fe",
[":fairy_tone5:"] = "\U0001f9da\U0001f3ff",
[":fairy_dark_skin_tone:"] = "\U0001f9da\U0001f3ff",
[":fairy::skin-tone-5:"] = "\U0001f9da\U0001f3ff",
[":falafel:"] = "\U0001f9c6",
[":fallen_leaf:"] = "\U0001f342",
[":family:"] = "\U0001f46a",
[":family_man_boy:"] = "\U0001f468\u200d\U0001f466",
[":family_man_boy_boy:"] = "\U0001f468\u200d\U0001f466\u200d\U0001f466",
[":family_man_girl:"] = "\U0001f468\u200d\U0001f467",
[":family_man_girl_boy:"] = "\U0001f468\u200d\U0001f467\u200d\U0001f466",
[":family_man_girl_girl:"] = "\U0001f468\u200d\U0001f467\u200d\U0001f467",
[":family_man_woman_boy:"] = "\U0001f468\u200d\U0001f469\u200d\U0001f466",
[":family_mmb:"] = "\U0001f468\u200d\U0001f468\u200d\U0001f466",
[":family_mmbb:"] = "\U0001f468\u200d\U0001f468\u200d\U0001f466\u200d\U0001f466",
[":family_mmg:"] = "\U0001f468\u200d\U0001f468\u200d\U0001f467",
[":family_mmgb:"] = "\U0001f468\u200d\U0001f468\u200d\U0001f467\u200d\U0001f466",
[":family_mmgg:"] = "\U0001f468\u200d\U0001f468\u200d\U0001f467\u200d\U0001f467",
[":family_mwbb:"] = "\U0001f468\u200d\U0001f469\u200d\U0001f466\u200d\U0001f466",
[":family_mwg:"] = "\U0001f468\u200d\U0001f469\u200d\U0001f467",
[":family_mwgb:"] = "\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f466",
[":family_mwgg:"] = "\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f467",
[":family_woman_boy:"] = "\U0001f469\u200d\U0001f466",
[":family_woman_boy_boy:"] = "\U0001f469\u200d\U0001f466\u200d\U0001f466",
[":family_woman_girl:"] = "\U0001f469\u200d\U0001f467",
[":family_woman_girl_boy:"] = "\U0001f469\u200d\U0001f467\u200d\U0001f466",
[":family_woman_girl_girl:"] = "\U0001f469\u200d\U0001f467\u200d\U0001f467",
[":family_wwb:"] = "\U0001f469\u200d\U0001f469\u200d\U0001f466",
[":family_wwbb:"] = "\U0001f469\u200d\U0001f469\u200d\U0001f466\u200d\U0001f466",
[":family_wwg:"] = "\U0001f469\u200d\U0001f469\u200d\U0001f467",
[":family_wwgb:"] = "\U0001f469\u200d\U0001f469\u200d\U0001f467\u200d\U0001f466",
[":family_wwgg:"] = "\U0001f469\u200d\U0001f469\u200d\U0001f467\u200d\U0001f467",
[":farmer:"] = "\U0001f9d1\u200d\U0001f33e",
[":farmer_tone1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f33e",
[":farmer_light_skin_tone:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f33e",
[":farmer::skin-tone-1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f33e",
[":farmer_tone2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f33e",
[":farmer_medium_light_skin_tone:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f33e",
[":farmer::skin-tone-2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f33e",
[":farmer_tone3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f33e",
[":farmer_medium_skin_tone:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f33e",
[":farmer::skin-tone-3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f33e",
[":farmer_tone4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f33e",
[":farmer_medium_dark_skin_tone:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f33e",
[":farmer::skin-tone-4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f33e",
[":farmer_tone5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f33e",
[":farmer_dark_skin_tone:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f33e",
[":farmer::skin-tone-5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f33e",
[":fast_forward:"] = "\u23e9",
[":fax:"] = "\U0001f4e0",
[":fearful:"] = "\U0001f628",
[":feather:"] = "\U0001fab6",
[":feet:"] = "\U0001f43e",
[":paw_prints:"] = "\U0001f43e",
[":female_sign:"] = "\u2640\ufe0f",
[":ferris_wheel:"] = "\U0001f3a1",
[":ferry:"] = "\u26f4\ufe0f",
[":field_hockey:"] = "\U0001f3d1",
[":file_cabinet:"] = "\U0001f5c4\ufe0f",
[":file_folder:"] = "\U0001f4c1",
[":film_frames:"] = "\U0001f39e\ufe0f",
[":fingers_crossed:"] = "\U0001f91e",
[":hand_with_index_and_middle_finger_crossed:"] = "\U0001f91e",
[":fingers_crossed_tone1:"] = "\U0001f91e\U0001f3fb",
[":hand_with_index_and_middle_fingers_crossed_tone1:"] = "\U0001f91e\U0001f3fb",
[":fingers_crossed::skin-tone-1:"] = "\U0001f91e\U0001f3fb",
[":hand_with_index_and_middle_finger_crossed::skin-tone-1:"] = "\U0001f91e\U0001f3fb",
[":fingers_crossed_tone2:"] = "\U0001f91e\U0001f3fc",
[":hand_with_index_and_middle_fingers_crossed_tone2:"] = "\U0001f91e\U0001f3fc",
[":fingers_crossed::skin-tone-2:"] = "\U0001f91e\U0001f3fc",
[":hand_with_index_and_middle_finger_crossed::skin-tone-2:"] = "\U0001f91e\U0001f3fc",
[":fingers_crossed_tone3:"] = "\U0001f91e\U0001f3fd",
[":hand_with_index_and_middle_fingers_crossed_tone3:"] = "\U0001f91e\U0001f3fd",
[":fingers_crossed::skin-tone-3:"] = "\U0001f91e\U0001f3fd",
[":hand_with_index_and_middle_finger_crossed::skin-tone-3:"] = "\U0001f91e\U0001f3fd",
[":fingers_crossed_tone4:"] = "\U0001f91e\U0001f3fe",
[":hand_with_index_and_middle_fingers_crossed_tone4:"] = "\U0001f91e\U0001f3fe",
[":fingers_crossed::skin-tone-4:"] = "\U0001f91e\U0001f3fe",
[":hand_with_index_and_middle_finger_crossed::skin-tone-4:"] = "\U0001f91e\U0001f3fe",
[":fingers_crossed_tone5:"] = "\U0001f91e\U0001f3ff",
[":hand_with_index_and_middle_fingers_crossed_tone5:"] = "\U0001f91e\U0001f3ff",
[":fingers_crossed::skin-tone-5:"] = "\U0001f91e\U0001f3ff",
[":hand_with_index_and_middle_finger_crossed::skin-tone-5:"] = "\U0001f91e\U0001f3ff",
[":fire:"] = "\U0001f525",
[":flame:"] = "\U0001f525",
[":fire_engine:"] = "\U0001f692",
[":fire_extinguisher:"] = "\U0001f9ef",
[":firecracker:"] = "\U0001f9e8",
[":firefighter:"] = "\U0001f9d1\u200d\U0001f692",
[":firefighter_tone1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f692",
[":firefighter_light_skin_tone:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f692",
[":firefighter::skin-tone-1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f692",
[":firefighter_tone2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f692",
[":firefighter_medium_light_skin_tone:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f692",
[":firefighter::skin-tone-2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f692",
[":firefighter_tone3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f692",
[":firefighter_medium_skin_tone:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f692",
[":firefighter::skin-tone-3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f692",
[":firefighter_tone4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f692",
[":firefighter_medium_dark_skin_tone:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f692",
[":firefighter::skin-tone-4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f692",
[":firefighter_tone5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f692",
[":firefighter_dark_skin_tone:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f692",
[":firefighter::skin-tone-5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f692",
[":fireworks:"] = "\U0001f386",
[":first_place:"] = "\U0001f947",
[":first_place_medal:"] = "\U0001f947",
[":first_quarter_moon:"] = "\U0001f313",
[":first_quarter_moon_with_face:"] = "\U0001f31b",
[":fish:"] = "\U0001f41f",
[":fish_cake:"] = "\U0001f365",
[":fishing_pole_and_fish:"] = "\U0001f3a3",
[":fist:"] = "\u270a",
[":fist_tone1:"] = "\u270a\U0001f3fb",
[":fist::skin-tone-1:"] = "\u270a\U0001f3fb",
[":fist_tone2:"] = "\u270a\U0001f3fc",
[":fist::skin-tone-2:"] = "\u270a\U0001f3fc",
[":fist_tone3:"] = "\u270a\U0001f3fd",
[":fist::skin-tone-3:"] = "\u270a\U0001f3fd",
[":fist_tone4:"] = "\u270a\U0001f3fe",
[":fist::skin-tone-4:"] = "\u270a\U0001f3fe",
[":fist_tone5:"] = "\u270a\U0001f3ff",
[":fist::skin-tone-5:"] = "\u270a\U0001f3ff",
[":five:"] = "\u0035\ufe0f\u20e3",
[":flag_ac:"] = "\U0001f1e6\U0001f1e8",
[":flag_ad:"] = "\U0001f1e6\U0001f1e9",
[":flag_ae:"] = "\U0001f1e6\U0001f1ea",
[":flag_af:"] = "\U0001f1e6\U0001f1eb",
[":flag_ag:"] = "\U0001f1e6\U0001f1ec",
[":flag_ai:"] = "\U0001f1e6\U0001f1ee",
[":flag_al:"] = "\U0001f1e6\U0001f1f1",
[":flag_am:"] = "\U0001f1e6\U0001f1f2",
[":flag_ao:"] = "\U0001f1e6\U0001f1f4",
[":flag_aq:"] = "\U0001f1e6\U0001f1f6",
[":flag_ar:"] = "\U0001f1e6\U0001f1f7",
[":flag_as:"] = "\U0001f1e6\U0001f1f8",
[":flag_at:"] = "\U0001f1e6\U0001f1f9",
[":flag_au:"] = "\U0001f1e6\U0001f1fa",
[":flag_aw:"] = "\U0001f1e6\U0001f1fc",
[":flag_ax:"] = "\U0001f1e6\U0001f1fd",
[":flag_az:"] = "\U0001f1e6\U0001f1ff",
[":flag_ba:"] = "\U0001f1e7\U0001f1e6",
[":flag_bb:"] = "\U0001f1e7\U0001f1e7",
[":flag_bd:"] = "\U0001f1e7\U0001f1e9",
[":flag_be:"] = "\U0001f1e7\U0001f1ea",
[":flag_bf:"] = "\U0001f1e7\U0001f1eb",
[":flag_bg:"] = "\U0001f1e7\U0001f1ec",
[":flag_bh:"] = "\U0001f1e7\U0001f1ed",
[":flag_bi:"] = "\U0001f1e7\U0001f1ee",
[":flag_bj:"] = "\U0001f1e7\U0001f1ef",
[":flag_bl:"] = "\U0001f1e7\U0001f1f1",
[":flag_black:"] = "\U0001f3f4",
[":flag_bm:"] = "\U0001f1e7\U0001f1f2",
[":flag_bn:"] = "\U0001f1e7\U0001f1f3",
[":flag_bo:"] = "\U0001f1e7\U0001f1f4",
[":flag_bq:"] = "\U0001f1e7\U0001f1f6",
[":flag_br:"] = "\U0001f1e7\U0001f1f7",
[":flag_bs:"] = "\U0001f1e7\U0001f1f8",
[":flag_bt:"] = "\U0001f1e7\U0001f1f9",
[":flag_bv:"] = "\U0001f1e7\U0001f1fb",
[":flag_bw:"] = "\U0001f1e7\U0001f1fc",
[":flag_by:"] = "\U0001f1e7\U0001f1fe",
[":flag_bz:"] = "\U0001f1e7\U0001f1ff",
[":flag_ca:"] = "\U0001f1e8\U0001f1e6",
[":flag_cc:"] = "\U0001f1e8\U0001f1e8",
[":flag_cd:"] = "\U0001f1e8\U0001f1e9",
[":flag_cf:"] = "\U0001f1e8\U0001f1eb",
[":flag_cg:"] = "\U0001f1e8\U0001f1ec",
[":flag_ch:"] = "\U0001f1e8\U0001f1ed",
[":flag_ci:"] = "\U0001f1e8\U0001f1ee",
[":flag_ck:"] = "\U0001f1e8\U0001f1f0",
[":flag_cl:"] = "\U0001f1e8\U0001f1f1",
[":flag_cm:"] = "\U0001f1e8\U0001f1f2",
[":flag_cn:"] = "\U0001f1e8\U0001f1f3",
[":flag_co:"] = "\U0001f1e8\U0001f1f4",
[":flag_cp:"] = "\U0001f1e8\U0001f1f5",
[":flag_cr:"] = "\U0001f1e8\U0001f1f7",
[":flag_cu:"] = "\U0001f1e8\U0001f1fa",
[":flag_cv:"] = "\U0001f1e8\U0001f1fb",
[":flag_cw:"] = "\U0001f1e8\U0001f1fc",
[":flag_cx:"] = "\U0001f1e8\U0001f1fd",
[":flag_cy:"] = "\U0001f1e8\U0001f1fe",
[":flag_cz:"] = "\U0001f1e8\U0001f1ff",
[":flag_de:"] = "\U0001f1e9\U0001f1ea",
[":flag_dg:"] = "\U0001f1e9\U0001f1ec",
[":flag_dj:"] = "\U0001f1e9\U0001f1ef",
[":flag_dk:"] = "\U0001f1e9\U0001f1f0",
[":flag_dm:"] = "\U0001f1e9\U0001f1f2",
[":flag_do:"] = "\U0001f1e9\U0001f1f4",
[":flag_dz:"] = "\U0001f1e9\U0001f1ff",
[":flag_ea:"] = "\U0001f1ea\U0001f1e6",
[":flag_ec:"] = "\U0001f1ea\U0001f1e8",
[":flag_ee:"] = "\U0001f1ea\U0001f1ea",
[":flag_eg:"] = "\U0001f1ea\U0001f1ec",
[":flag_eh:"] = "\U0001f1ea\U0001f1ed",
[":flag_er:"] = "\U0001f1ea\U0001f1f7",
[":flag_es:"] = "\U0001f1ea\U0001f1f8",
[":flag_et:"] = "\U0001f1ea\U0001f1f9",
[":flag_eu:"] = "\U0001f1ea\U0001f1fa",
[":flag_fi:"] = "\U0001f1eb\U0001f1ee",
[":flag_fj:"] = "\U0001f1eb\U0001f1ef",
[":flag_fk:"] = "\U0001f1eb\U0001f1f0",
[":flag_fm:"] = "\U0001f1eb\U0001f1f2",
[":flag_fo:"] = "\U0001f1eb\U0001f1f4",
[":flag_fr:"] = "\U0001f1eb\U0001f1f7",
[":flag_ga:"] = "\U0001f1ec\U0001f1e6",
[":flag_gb:"] = "\U0001f1ec\U0001f1e7",
[":flag_gd:"] = "\U0001f1ec\U0001f1e9",
[":flag_ge:"] = "\U0001f1ec\U0001f1ea",
[":flag_gf:"] = "\U0001f1ec\U0001f1eb",
[":flag_gg:"] = "\U0001f1ec\U0001f1ec",
[":flag_gh:"] = "\U0001f1ec\U0001f1ed",
[":flag_gi:"] = "\U0001f1ec\U0001f1ee",
[":flag_gl:"] = "\U0001f1ec\U0001f1f1",
[":flag_gm:"] = "\U0001f1ec\U0001f1f2",
[":flag_gn:"] = "\U0001f1ec\U0001f1f3",
[":flag_gp:"] = "\U0001f1ec\U0001f1f5",
[":flag_gq:"] = "\U0001f1ec\U0001f1f6",
[":flag_gr:"] = "\U0001f1ec\U0001f1f7",
[":flag_gs:"] = "\U0001f1ec\U0001f1f8",
[":flag_gt:"] = "\U0001f1ec\U0001f1f9",
[":flag_gu:"] = "\U0001f1ec\U0001f1fa",
[":flag_gw:"] = "\U0001f1ec\U0001f1fc",
[":flag_gy:"] = "\U0001f1ec\U0001f1fe",
[":flag_hk:"] = "\U0001f1ed\U0001f1f0",
[":flag_hm:"] = "\U0001f1ed\U0001f1f2",
[":flag_hn:"] = "\U0001f1ed\U0001f1f3",
[":flag_hr:"] = "\U0001f1ed\U0001f1f7",
[":flag_ht:"] = "\U0001f1ed\U0001f1f9",
[":flag_hu:"] = "\U0001f1ed\U0001f1fa",
[":flag_ic:"] = "\U0001f1ee\U0001f1e8",
[":flag_id:"] = "\U0001f1ee\U0001f1e9",
[":flag_ie:"] = "\U0001f1ee\U0001f1ea",
[":flag_il:"] = "\U0001f1ee\U0001f1f1",
[":flag_im:"] = "\U0001f1ee\U0001f1f2",
[":flag_in:"] = "\U0001f1ee\U0001f1f3",
[":flag_io:"] = "\U0001f1ee\U0001f1f4",
[":flag_iq:"] = "\U0001f1ee\U0001f1f6",
[":flag_ir:"] = "\U0001f1ee\U0001f1f7",
[":flag_is:"] = "\U0001f1ee\U0001f1f8",
[":flag_it:"] = "\U0001f1ee\U0001f1f9",
[":flag_je:"] = "\U0001f1ef\U0001f1ea",
[":flag_jm:"] = "\U0001f1ef\U0001f1f2",
[":flag_jo:"] = "\U0001f1ef\U0001f1f4",
[":flag_jp:"] = "\U0001f1ef\U0001f1f5",
[":flag_ke:"] = "\U0001f1f0\U0001f1ea",
[":flag_kg:"] = "\U0001f1f0\U0001f1ec",
[":flag_kh:"] = "\U0001f1f0\U0001f1ed",
[":flag_ki:"] = "\U0001f1f0\U0001f1ee",
[":flag_km:"] = "\U0001f1f0\U0001f1f2",
[":flag_kn:"] = "\U0001f1f0\U0001f1f3",
[":flag_kp:"] = "\U0001f1f0\U0001f1f5",
[":flag_kr:"] = "\U0001f1f0\U0001f1f7",
[":flag_kw:"] = "\U0001f1f0\U0001f1fc",
[":flag_ky:"] = "\U0001f1f0\U0001f1fe",
[":flag_kz:"] = "\U0001f1f0\U0001f1ff",
[":flag_la:"] = "\U0001f1f1\U0001f1e6",
[":flag_lb:"] = "\U0001f1f1\U0001f1e7",
[":flag_lc:"] = "\U0001f1f1\U0001f1e8",
[":flag_li:"] = "\U0001f1f1\U0001f1ee",
[":flag_lk:"] = "\U0001f1f1\U0001f1f0",
[":flag_lr:"] = "\U0001f1f1\U0001f1f7",
[":flag_ls:"] = "\U0001f1f1\U0001f1f8",
[":flag_lt:"] = "\U0001f1f1\U0001f1f9",
[":flag_lu:"] = "\U0001f1f1\U0001f1fa",
[":flag_lv:"] = "\U0001f1f1\U0001f1fb",
[":flag_ly:"] = "\U0001f1f1\U0001f1fe",
[":flag_ma:"] = "\U0001f1f2\U0001f1e6",
[":flag_mc:"] = "\U0001f1f2\U0001f1e8",
[":flag_md:"] = "\U0001f1f2\U0001f1e9",
[":flag_me:"] = "\U0001f1f2\U0001f1ea",
[":flag_mf:"] = "\U0001f1f2\U0001f1eb",
[":flag_mg:"] = "\U0001f1f2\U0001f1ec",
[":flag_mh:"] = "\U0001f1f2\U0001f1ed",
[":flag_mk:"] = "\U0001f1f2\U0001f1f0",
[":flag_ml:"] = "\U0001f1f2\U0001f1f1",
[":flag_mm:"] = "\U0001f1f2\U0001f1f2",
[":flag_mn:"] = "\U0001f1f2\U0001f1f3",
[":flag_mo:"] = "\U0001f1f2\U0001f1f4",
[":flag_mp:"] = "\U0001f1f2\U0001f1f5",
[":flag_mq:"] = "\U0001f1f2\U0001f1f6",
[":flag_mr:"] = "\U0001f1f2\U0001f1f7",
[":flag_ms:"] = "\U0001f1f2\U0001f1f8",
[":flag_mt:"] = "\U0001f1f2\U0001f1f9",
[":flag_mu:"] = "\U0001f1f2\U0001f1fa",
[":flag_mv:"] = "\U0001f1f2\U0001f1fb",
[":flag_mw:"] = "\U0001f1f2\U0001f1fc",
[":flag_mx:"] = "\U0001f1f2\U0001f1fd",
[":flag_my:"] = "\U0001f1f2\U0001f1fe",
[":flag_mz:"] = "\U0001f1f2\U0001f1ff",
[":flag_na:"] = "\U0001f1f3\U0001f1e6",
[":flag_nc:"] = "\U0001f1f3\U0001f1e8",
[":flag_ne:"] = "\U0001f1f3\U0001f1ea",
[":flag_nf:"] = "\U0001f1f3\U0001f1eb",
[":flag_ng:"] = "\U0001f1f3\U0001f1ec",
[":flag_ni:"] = "\U0001f1f3\U0001f1ee",
[":flag_nl:"] = "\U0001f1f3\U0001f1f1",
[":flag_no:"] = "\U0001f1f3\U0001f1f4",
[":flag_np:"] = "\U0001f1f3\U0001f1f5",
[":flag_nr:"] = "\U0001f1f3\U0001f1f7",
[":flag_nu:"] = "\U0001f1f3\U0001f1fa",
[":flag_nz:"] = "\U0001f1f3\U0001f1ff",
[":flag_om:"] = "\U0001f1f4\U0001f1f2",
[":flag_pa:"] = "\U0001f1f5\U0001f1e6",
[":flag_pe:"] = "\U0001f1f5\U0001f1ea",
[":flag_pf:"] = "\U0001f1f5\U0001f1eb",
[":flag_pg:"] = "\U0001f1f5\U0001f1ec",
[":flag_ph:"] = "\U0001f1f5\U0001f1ed",
[":flag_pk:"] = "\U0001f1f5\U0001f1f0",
[":flag_pl:"] = "\U0001f1f5\U0001f1f1",
[":flag_pm:"] = "\U0001f1f5\U0001f1f2",
[":flag_pn:"] = "\U0001f1f5\U0001f1f3",
[":flag_pr:"] = "\U0001f1f5\U0001f1f7",
[":flag_ps:"] = "\U0001f1f5\U0001f1f8",
[":flag_pt:"] = "\U0001f1f5\U0001f1f9",
[":flag_pw:"] = "\U0001f1f5\U0001f1fc",
[":flag_py:"] = "\U0001f1f5\U0001f1fe",
[":flag_qa:"] = "\U0001f1f6\U0001f1e6",
[":flag_re:"] = "\U0001f1f7\U0001f1ea",
[":flag_ro:"] = "\U0001f1f7\U0001f1f4",
[":flag_rs:"] = "\U0001f1f7\U0001f1f8",
[":flag_ru:"] = "\U0001f1f7\U0001f1fa",
[":flag_rw:"] = "\U0001f1f7\U0001f1fc",
[":flag_sa:"] = "\U0001f1f8\U0001f1e6",
[":flag_sb:"] = "\U0001f1f8\U0001f1e7",
[":flag_sc:"] = "\U0001f1f8\U0001f1e8",
[":flag_sd:"] = "\U0001f1f8\U0001f1e9",
[":flag_se:"] = "\U0001f1f8\U0001f1ea",
[":flag_sg:"] = "\U0001f1f8\U0001f1ec",
[":flag_sh:"] = "\U0001f1f8\U0001f1ed",
[":flag_si:"] = "\U0001f1f8\U0001f1ee",
[":flag_sj:"] = "\U0001f1f8\U0001f1ef",
[":flag_sk:"] = "\U0001f1f8\U0001f1f0",
[":flag_sl:"] = "\U0001f1f8\U0001f1f1",
[":flag_sm:"] = "\U0001f1f8\U0001f1f2",
[":flag_sn:"] = "\U0001f1f8\U0001f1f3",
[":flag_so:"] = "\U0001f1f8\U0001f1f4",
[":flag_sr:"] = "\U0001f1f8\U0001f1f7",
[":flag_ss:"] = "\U0001f1f8\U0001f1f8",
[":flag_st:"] = "\U0001f1f8\U0001f1f9",
[":flag_sv:"] = "\U0001f1f8\U0001f1fb",
[":flag_sx:"] = "\U0001f1f8\U0001f1fd",
[":flag_sy:"] = "\U0001f1f8\U0001f1fe",
[":flag_sz:"] = "\U0001f1f8\U0001f1ff",
[":flag_ta:"] = "\U0001f1f9\U0001f1e6",
[":flag_tc:"] = "\U0001f1f9\U0001f1e8",
[":flag_td:"] = "\U0001f1f9\U0001f1e9",
[":flag_tf:"] = "\U0001f1f9\U0001f1eb",
[":flag_tg:"] = "\U0001f1f9\U0001f1ec",
[":flag_th:"] = "\U0001f1f9\U0001f1ed",
[":flag_tj:"] = "\U0001f1f9\U0001f1ef",
[":flag_tk:"] = "\U0001f1f9\U0001f1f0",
[":flag_tl:"] = "\U0001f1f9\U0001f1f1",
[":flag_tm:"] = "\U0001f1f9\U0001f1f2",
[":flag_tn:"] = "\U0001f1f9\U0001f1f3",
[":flag_to:"] = "\U0001f1f9\U0001f1f4",
[":flag_tr:"] = "\U0001f1f9\U0001f1f7",
[":flag_tt:"] = "\U0001f1f9\U0001f1f9",
[":flag_tv:"] = "\U0001f1f9\U0001f1fb",
[":flag_tw:"] = "\U0001f1f9\U0001f1fc",
[":flag_tz:"] = "\U0001f1f9\U0001f1ff",
[":flag_ua:"] = "\U0001f1fa\U0001f1e6",
[":flag_ug:"] = "\U0001f1fa\U0001f1ec",
[":flag_um:"] = "\U0001f1fa\U0001f1f2",
[":flag_us:"] = "\U0001f1fa\U0001f1f8",
[":flag_uy:"] = "\U0001f1fa\U0001f1fe",
[":flag_uz:"] = "\U0001f1fa\U0001f1ff",
[":flag_va:"] = "\U0001f1fb\U0001f1e6",
[":flag_vc:"] = "\U0001f1fb\U0001f1e8",
[":flag_ve:"] = "\U0001f1fb\U0001f1ea",
[":flag_vg:"] = "\U0001f1fb\U0001f1ec",
[":flag_vi:"] = "\U0001f1fb\U0001f1ee",
[":flag_vn:"] = "\U0001f1fb\U0001f1f3",
[":flag_vu:"] = "\U0001f1fb\U0001f1fa",
[":flag_wf:"] = "\U0001f1fc\U0001f1eb",
[":flag_white:"] = "\U0001f3f3\ufe0f",
[":flag_ws:"] = "\U0001f1fc\U0001f1f8",
[":flag_xk:"] = "\U0001f1fd\U0001f1f0",
[":flag_ye:"] = "\U0001f1fe\U0001f1ea",
[":flag_yt:"] = "\U0001f1fe\U0001f1f9",
[":flag_za:"] = "\U0001f1ff\U0001f1e6",
[":flag_zm:"] = "\U0001f1ff\U0001f1f2",
[":flag_zw:"] = "\U0001f1ff\U0001f1fc",
[":flags:"] = "\U0001f38f",
[":flamingo:"] = "\U0001f9a9",
[":flashlight:"] = "\U0001f526",
[":flatbread:"] = "\U0001fad3",
[":fleur_de_lis:"] = "\u269c\ufe0f",
[":floppy_disk:"] = "\U0001f4be",
[":flower_playing_cards:"] = "\U0001f3b4",
[":flushed:"] = "\U0001f633",
[":fly:"] = "\U0001fab0",
[":flying_disc:"] = "\U0001f94f",
[":flying_saucer:"] = "\U0001f6f8",
[":fog:"] = "\U0001f32b\ufe0f",
[":foggy:"] = "\U0001f301",
[":fondue:"] = "\U0001fad5",
[":foot:"] = "\U0001f9b6",
[":foot_tone1:"] = "\U0001f9b6\U0001f3fb",
[":foot_light_skin_tone:"] = "\U0001f9b6\U0001f3fb",
[":foot::skin-tone-1:"] = "\U0001f9b6\U0001f3fb",
[":foot_tone2:"] = "\U0001f9b6\U0001f3fc",
[":foot_medium_light_skin_tone:"] = "\U0001f9b6\U0001f3fc",
[":foot::skin-tone-2:"] = "\U0001f9b6\U0001f3fc",
[":foot_tone3:"] = "\U0001f9b6\U0001f3fd",
[":foot_medium_skin_tone:"] = "\U0001f9b6\U0001f3fd",
[":foot::skin-tone-3:"] = "\U0001f9b6\U0001f3fd",
[":foot_tone4:"] = "\U0001f9b6\U0001f3fe",
[":foot_medium_dark_skin_tone:"] = "\U0001f9b6\U0001f3fe",
[":foot::skin-tone-4:"] = "\U0001f9b6\U0001f3fe",
[":foot_tone5:"] = "\U0001f9b6\U0001f3ff",
[":foot_dark_skin_tone:"] = "\U0001f9b6\U0001f3ff",
[":foot::skin-tone-5:"] = "\U0001f9b6\U0001f3ff",
[":football:"] = "\U0001f3c8",
[":footprints:"] = "\U0001f463",
[":fork_and_knife:"] = "\U0001f374",
[":fork_knife_plate:"] = "\U0001f37d\ufe0f",
[":fork_and_knife_with_plate:"] = "\U0001f37d\ufe0f",
[":fortune_cookie:"] = "\U0001f960",
[":fountain:"] = "\u26f2",
[":four:"] = "\u0034\ufe0f\u20e3",
[":four_leaf_clover:"] = "\U0001f340",
[":fox:"] = "\U0001f98a",
[":fox_face:"] = "\U0001f98a",
[":frame_photo:"] = "\U0001f5bc\ufe0f",
[":frame_with_picture:"] = "\U0001f5bc\ufe0f",
[":free:"] = "\U0001f193",
[":french_bread:"] = "\U0001f956",
[":baguette_bread:"] = "\U0001f956",
[":fried_shrimp:"] = "\U0001f364",
[":fries:"] = "\U0001f35f",
[":frog:"] = "\U0001f438",
[":frowning:"] = "\U0001f626",
[":("] = "\U0001f626",
[":-("] = "\U0001f626",
["=("] = "\U0001f626",
["=-("] = "\U0001f626",
[":frowning2:"] = "\u2639\ufe0f",
[":white_frowning_face:"] = "\u2639\ufe0f",
[":fuelpump:"] = "\u26fd",
[":full_moon:"] = "\U0001f315",
[":full_moon_with_face:"] = "\U0001f31d",
[":game_die:"] = "\U0001f3b2",
[":garlic:"] = "\U0001f9c4",
[":gear:"] = "\u2699\ufe0f",
[":gem:"] = "\U0001f48e",
[":gemini:"] = "\u264a",
[":genie:"] = "\U0001f9de",
[":ghost:"] = "\U0001f47b",
[":gift:"] = "\U0001f381",
[":gift_heart:"] = "\U0001f49d",
[":giraffe:"] = "\U0001f992",
[":girl:"] = "\U0001f467",
[":girl_tone1:"] = "\U0001f467\U0001f3fb",
[":girl::skin-tone-1:"] = "\U0001f467\U0001f3fb",
[":girl_tone2:"] = "\U0001f467\U0001f3fc",
[":girl::skin-tone-2:"] = "\U0001f467\U0001f3fc",
[":girl_tone3:"] = "\U0001f467\U0001f3fd",
[":girl::skin-tone-3:"] = "\U0001f467\U0001f3fd",
[":girl_tone4:"] = "\U0001f467\U0001f3fe",
[":girl::skin-tone-4:"] = "\U0001f467\U0001f3fe",
[":girl_tone5:"] = "\U0001f467\U0001f3ff",
[":girl::skin-tone-5:"] = "\U0001f467\U0001f3ff",
[":globe_with_meridians:"] = "\U0001f310",
[":gloves:"] = "\U0001f9e4",
[":goal:"] = "\U0001f945",
[":goal_net:"] = "\U0001f945",
[":goat:"] = "\U0001f410",
[":goggles:"] = "\U0001f97d",
[":golf:"] = "\u26f3",
[":gorilla:"] = "\U0001f98d",
[":grapes:"] = "\U0001f347",
[":green_apple:"] = "\U0001f34f",
[":green_book:"] = "\U0001f4d7",
[":green_circle:"] = "\U0001f7e2",
[":green_heart:"] = "\U0001f49a",
[":green_square:"] = "\U0001f7e9",
[":grey_exclamation:"] = "\u2755",
[":grey_question:"] = "\u2754",
[":grimacing:"] = "\U0001f62c",
[":grin:"] = "\U0001f601",
[":grinning:"] = "\U0001f600",
[":guard:"] = "\U0001f482",
[":guardsman:"] = "\U0001f482",
[":guard_tone1:"] = "\U0001f482\U0001f3fb",
[":guardsman_tone1:"] = "\U0001f482\U0001f3fb",
[":guard::skin-tone-1:"] = "\U0001f482\U0001f3fb",
[":guardsman::skin-tone-1:"] = "\U0001f482\U0001f3fb",
[":guard_tone2:"] = "\U0001f482\U0001f3fc",
[":guardsman_tone2:"] = "\U0001f482\U0001f3fc",
[":guard::skin-tone-2:"] = "\U0001f482\U0001f3fc",
[":guardsman::skin-tone-2:"] = "\U0001f482\U0001f3fc",
[":guard_tone3:"] = "\U0001f482\U0001f3fd",
[":guardsman_tone3:"] = "\U0001f482\U0001f3fd",
[":guard::skin-tone-3:"] = "\U0001f482\U0001f3fd",
[":guardsman::skin-tone-3:"] = "\U0001f482\U0001f3fd",
[":guard_tone4:"] = "\U0001f482\U0001f3fe",
[":guardsman_tone4:"] = "\U0001f482\U0001f3fe",
[":guard::skin-tone-4:"] = "\U0001f482\U0001f3fe",
[":guardsman::skin-tone-4:"] = "\U0001f482\U0001f3fe",
[":guard_tone5:"] = "\U0001f482\U0001f3ff",
[":guardsman_tone5:"] = "\U0001f482\U0001f3ff",
[":guard::skin-tone-5:"] = "\U0001f482\U0001f3ff",
[":guardsman::skin-tone-5:"] = "\U0001f482\U0001f3ff",
[":guide_dog:"] = "\U0001f9ae",
[":guitar:"] = "\U0001f3b8",
[":gun:"] = "\U0001f52b",
[":hamburger:"] = "\U0001f354",
[":hammer:"] = "\U0001f528",
[":hammer_pick:"] = "\u2692\ufe0f",
[":hammer_and_pick:"] = "\u2692\ufe0f",
[":hamster:"] = "\U0001f439",
[":hand_splayed:"] = "\U0001f590\ufe0f",
[":raised_hand_with_fingers_splayed:"] = "\U0001f590\ufe0f",
[":hand_splayed_tone1:"] = "\U0001f590\U0001f3fb",
[":raised_hand_with_fingers_splayed_tone1:"] = "\U0001f590\U0001f3fb",
[":hand_splayed::skin-tone-1:"] = "\U0001f590\U0001f3fb",
[":raised_hand_with_fingers_splayed::skin-tone-1:"] = "\U0001f590\U0001f3fb",
[":hand_splayed_tone2:"] = "\U0001f590\U0001f3fc",
[":raised_hand_with_fingers_splayed_tone2:"] = "\U0001f590\U0001f3fc",
[":hand_splayed::skin-tone-2:"] = "\U0001f590\U0001f3fc",
[":raised_hand_with_fingers_splayed::skin-tone-2:"] = "\U0001f590\U0001f3fc",
[":hand_splayed_tone3:"] = "\U0001f590\U0001f3fd",
[":raised_hand_with_fingers_splayed_tone3:"] = "\U0001f590\U0001f3fd",
[":hand_splayed::skin-tone-3:"] = "\U0001f590\U0001f3fd",
[":raised_hand_with_fingers_splayed::skin-tone-3:"] = "\U0001f590\U0001f3fd",
[":hand_splayed_tone4:"] = "\U0001f590\U0001f3fe",
[":raised_hand_with_fingers_splayed_tone4:"] = "\U0001f590\U0001f3fe",
[":hand_splayed::skin-tone-4:"] = "\U0001f590\U0001f3fe",
[":raised_hand_with_fingers_splayed::skin-tone-4:"] = "\U0001f590\U0001f3fe",
[":hand_splayed_tone5:"] = "\U0001f590\U0001f3ff",
[":raised_hand_with_fingers_splayed_tone5:"] = "\U0001f590\U0001f3ff",
[":hand_splayed::skin-tone-5:"] = "\U0001f590\U0001f3ff",
[":raised_hand_with_fingers_splayed::skin-tone-5:"] = "\U0001f590\U0001f3ff",
[":handbag:"] = "\U0001f45c",
[":handshake:"] = "\U0001f91d",
[":shaking_hands:"] = "\U0001f91d",
[":hash:"] = "\u0023\ufe0f\u20e3",
[":hatched_chick:"] = "\U0001f425",
[":hatching_chick:"] = "\U0001f423",
[":head_bandage:"] = "\U0001f915",
[":face_with_head_bandage:"] = "\U0001f915",
[":headphones:"] = "\U0001f3a7",
[":headstone:"] = "\U0001faa6",
[":health_worker:"] = "\U0001f9d1\u200d\u2695\ufe0f",
[":health_worker_tone1:"] = "\U0001f9d1\U0001f3fb\u200d\u2695\ufe0f",
[":health_worker_light_skin_tone:"] = "\U0001f9d1\U0001f3fb\u200d\u2695\ufe0f",
[":health_worker::skin-tone-1:"] = "\U0001f9d1\U0001f3fb\u200d\u2695\ufe0f",
[":health_worker_tone2:"] = "\U0001f9d1\U0001f3fc\u200d\u2695\ufe0f",
[":health_worker_medium_light_skin_tone:"] = "\U0001f9d1\U0001f3fc\u200d\u2695\ufe0f",
[":health_worker::skin-tone-2:"] = "\U0001f9d1\U0001f3fc\u200d\u2695\ufe0f",
[":health_worker_tone3:"] = "\U0001f9d1\U0001f3fd\u200d\u2695\ufe0f",
[":health_worker_medium_skin_tone:"] = "\U0001f9d1\U0001f3fd\u200d\u2695\ufe0f",
[":health_worker::skin-tone-3:"] = "\U0001f9d1\U0001f3fd\u200d\u2695\ufe0f",
[":health_worker_tone4:"] = "\U0001f9d1\U0001f3fe\u200d\u2695\ufe0f",
[":health_worker_medium_dark_skin_tone:"] = "\U0001f9d1\U0001f3fe\u200d\u2695\ufe0f",
[":health_worker::skin-tone-4:"] = "\U0001f9d1\U0001f3fe\u200d\u2695\ufe0f",
[":health_worker_tone5:"] = "\U0001f9d1\U0001f3ff\u200d\u2695\ufe0f",
[":health_worker_dark_skin_tone:"] = "\U0001f9d1\U0001f3ff\u200d\u2695\ufe0f",
[":health_worker::skin-tone-5:"] = "\U0001f9d1\U0001f3ff\u200d\u2695\ufe0f",
[":hear_no_evil:"] = "\U0001f649",
[":heart:"] = "\u2764\ufe0f",
["<3"] = "\u2764\ufe0f",
["♡"] = "\u2764\ufe0f",
[":heart_decoration:"] = "\U0001f49f",
[":heart_exclamation:"] = "\u2763\ufe0f",
[":heavy_heart_exclamation_mark_ornament:"] = "\u2763\ufe0f",
[":heart_eyes:"] = "\U0001f60d",
[":heart_eyes_cat:"] = "\U0001f63b",
[":heartbeat:"] = "\U0001f493",
[":heartpulse:"] = "\U0001f497",
[":hearts:"] = "\u2665\ufe0f",
[":heavy_check_mark:"] = "\u2714\ufe0f",
[":heavy_division_sign:"] = "\u2797",
[":heavy_dollar_sign:"] = "\U0001f4b2",
[":heavy_minus_sign:"] = "\u2796",
[":heavy_multiplication_x:"] = "\u2716\ufe0f",
[":heavy_plus_sign:"] = "\u2795",
[":hedgehog:"] = "\U0001f994",
[":helicopter:"] = "\U0001f681",
[":helmet_with_cross:"] = "\u26d1\ufe0f",
[":helmet_with_white_cross:"] = "\u26d1\ufe0f",
[":herb:"] = "\U0001f33f",
[":hibiscus:"] = "\U0001f33a",
[":high_brightness:"] = "\U0001f506",
[":high_heel:"] = "\U0001f460",
[":hiking_boot:"] = "\U0001f97e",
[":hindu_temple:"] = "\U0001f6d5",
[":hippopotamus:"] = "\U0001f99b",
[":hockey:"] = "\U0001f3d2",
[":hole:"] = "\U0001f573\ufe0f",
[":homes:"] = "\U0001f3d8\ufe0f",
[":house_buildings:"] = "\U0001f3d8\ufe0f",
[":honey_pot:"] = "\U0001f36f",
[":hook:"] = "\U0001fa9d",
[":horse:"] = "\U0001f434",
[":horse_racing:"] = "\U0001f3c7",
[":horse_racing_tone1:"] = "\U0001f3c7\U0001f3fb",
[":horse_racing::skin-tone-1:"] = "\U0001f3c7\U0001f3fb",
[":horse_racing_tone2:"] = "\U0001f3c7\U0001f3fc",
[":horse_racing::skin-tone-2:"] = "\U0001f3c7\U0001f3fc",
[":horse_racing_tone3:"] = "\U0001f3c7\U0001f3fd",
[":horse_racing::skin-tone-3:"] = "\U0001f3c7\U0001f3fd",
[":horse_racing_tone4:"] = "\U0001f3c7\U0001f3fe",
[":horse_racing::skin-tone-4:"] = "\U0001f3c7\U0001f3fe",
[":horse_racing_tone5:"] = "\U0001f3c7\U0001f3ff",
[":horse_racing::skin-tone-5:"] = "\U0001f3c7\U0001f3ff",
[":hospital:"] = "\U0001f3e5",
[":hot_face:"] = "\U0001f975",
[":hot_pepper:"] = "\U0001f336\ufe0f",
[":hotdog:"] = "\U0001f32d",
[":hot_dog:"] = "\U0001f32d",
[":hotel:"] = "\U0001f3e8",
[":hotsprings:"] = "\u2668\ufe0f",
[":hourglass:"] = "\u231b",
[":hourglass_flowing_sand:"] = "\u23f3",
[":house:"] = "\U0001f3e0",
[":house_abandoned:"] = "\U0001f3da\ufe0f",
[":derelict_house_building:"] = "\U0001f3da\ufe0f",
[":house_with_garden:"] = "\U0001f3e1",
[":hugging:"] = "\U0001f917",
[":hugging_face:"] = "\U0001f917",
[":hushed:"] = "\U0001f62f",
[":hut:"] = "\U0001f6d6",
[":ice_cream:"] = "\U0001f368",
[":ice_cube:"] = "\U0001f9ca",
[":ice_skate:"] = "\u26f8\ufe0f",
[":icecream:"] = "\U0001f366",
[":id:"] = "\U0001f194",
[":ideograph_advantage:"] = "\U0001f250",
[":imp:"] = "\U0001f47f",
["]:("] = "\U0001f47f",
["]:-("] = "\U0001f47f",
["]=("] = "\U0001f47f",
["]=-("] = "\U0001f47f",
[":inbox_tray:"] = "\U0001f4e5",
[":incoming_envelope:"] = "\U0001f4e8",
[":infinity:"] = "\u267e\ufe0f",
[":information_source:"] = "\u2139\ufe0f",
[":innocent:"] = "\U0001f607",
["o:)"] = "\U0001f607",
["O:)"] = "\U0001f607",
["o:-)"] = "\U0001f607",
["O:-)"] = "\U0001f607",
["0:)"] = "\U0001f607",
["0:-)"] = "\U0001f607",
["o=)"] = "\U0001f607",
["O=)"] = "\U0001f607",
["o=-)"] = "\U0001f607",
["O=-)"] = "\U0001f607",
["0=)"] = "\U0001f607",
["0=-)"] = "\U0001f607",
[":interrobang:"] = "\u2049\ufe0f",
[":island:"] = "\U0001f3dd\ufe0f",
[":desert_island:"] = "\U0001f3dd\ufe0f",
[":izakaya_lantern:"] = "\U0001f3ee",
[":jack_o_lantern:"] = "\U0001f383",
[":japan:"] = "\U0001f5fe",
[":japanese_castle:"] = "\U0001f3ef",
[":japanese_goblin:"] = "\U0001f47a",
[":japanese_ogre:"] = "\U0001f479",
[":jeans:"] = "\U0001f456",
[":jigsaw:"] = "\U0001f9e9",
[":joy:"] = "\U0001f602",
[":')"] = "\U0001f602",
[":'-)"] = "\U0001f602",
[":,)"] = "\U0001f602",
[":,-)"] = "\U0001f602",
[":'D"] = "\U0001f602",
[":'-D"] = "\U0001f602",
[":,D"] = "\U0001f602",
[":,-D"] = "\U0001f602",
["=')"] = "\U0001f602",
["='-)"] = "\U0001f602",
["=,)"] = "\U0001f602",
["=,-)"] = "\U0001f602",
["='D"] = "\U0001f602",
["='-D"] = "\U0001f602",
["=,D"] = "\U0001f602",
["=,-D"] = "\U0001f602",
[":joy_cat:"] = "\U0001f639",
[":joystick:"] = "\U0001f579\ufe0f",
[":judge:"] = "\U0001f9d1\u200d\u2696\ufe0f",
[":judge_tone1:"] = "\U0001f9d1\U0001f3fb\u200d\u2696\ufe0f",
[":judge_light_skin_tone:"] = "\U0001f9d1\U0001f3fb\u200d\u2696\ufe0f",
[":judge::skin-tone-1:"] = "\U0001f9d1\U0001f3fb\u200d\u2696\ufe0f",
[":judge_tone2:"] = "\U0001f9d1\U0001f3fc\u200d\u2696\ufe0f",
[":judge_medium_light_skin_tone:"] = "\U0001f9d1\U0001f3fc\u200d\u2696\ufe0f",
[":judge::skin-tone-2:"] = "\U0001f9d1\U0001f3fc\u200d\u2696\ufe0f",
[":judge_tone3:"] = "\U0001f9d1\U0001f3fd\u200d\u2696\ufe0f",
[":judge_medium_skin_tone:"] = "\U0001f9d1\U0001f3fd\u200d\u2696\ufe0f",
[":judge::skin-tone-3:"] = "\U0001f9d1\U0001f3fd\u200d\u2696\ufe0f",
[":judge_tone4:"] = "\U0001f9d1\U0001f3fe\u200d\u2696\ufe0f",
[":judge_medium_dark_skin_tone:"] = "\U0001f9d1\U0001f3fe\u200d\u2696\ufe0f",
[":judge::skin-tone-4:"] = "\U0001f9d1\U0001f3fe\u200d\u2696\ufe0f",
[":judge_tone5:"] = "\U0001f9d1\U0001f3ff\u200d\u2696\ufe0f",
[":judge_dark_skin_tone:"] = "\U0001f9d1\U0001f3ff\u200d\u2696\ufe0f",
[":judge::skin-tone-5:"] = "\U0001f9d1\U0001f3ff\u200d\u2696\ufe0f",
[":kaaba:"] = "\U0001f54b",
[":kangaroo:"] = "\U0001f998",
[":key:"] = "\U0001f511",
[":key2:"] = "\U0001f5dd\ufe0f",
[":old_key:"] = "\U0001f5dd\ufe0f",
[":keyboard:"] = "\u2328\ufe0f",
[":keycap_ten:"] = "\U0001f51f",
[":kimono:"] = "\U0001f458",
[":kiss:"] = "\U0001f48b",
[":kiss_mm:"] = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468",
[":couplekiss_mm:"] = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468",
[":kiss_woman_man:"] = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468",
[":kiss_ww:"] = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469",
[":couplekiss_ww:"] = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469",
[":kissing:"] = "\U0001f617",
[":*"] = "\U0001f617",
[":-*"] = "\U0001f617",
["=*"] = "\U0001f617",
["=-*"] = "\U0001f617",
[":kissing_cat:"] = "\U0001f63d",
[":kissing_closed_eyes:"] = "\U0001f61a",
[":kissing_heart:"] = "\U0001f618",
[":kissing_smiling_eyes:"] = "\U0001f619",
[":kite:"] = "\U0001fa81",
[":kiwi:"] = "\U0001f95d",
[":kiwifruit:"] = "\U0001f95d",
[":knife:"] = "\U0001f52a",
[":knot:"] = "\U0001faa2",
[":koala:"] = "\U0001f428",
[":koko:"] = "\U0001f201",
[":lab_coat:"] = "\U0001f97c",
[":label:"] = "\U0001f3f7\ufe0f",
[":lacrosse:"] = "\U0001f94d",
[":ladder:"] = "\U0001fa9c",
[":lady_beetle:"] = "\U0001f41e",
[":large_blue_diamond:"] = "\U0001f537",
[":large_orange_diamond:"] = "\U0001f536",
[":last_quarter_moon:"] = "\U0001f317",
[":last_quarter_moon_with_face:"] = "\U0001f31c",
[":laughing:"] = "\U0001f606",
[":satisfied:"] = "\U0001f606",
["x-)"] = "\U0001f606",
["X-)"] = "\U0001f606",
[":leafy_green:"] = "\U0001f96c",
[":leaves:"] = "\U0001f343",
[":ledger:"] = "\U0001f4d2",
[":left_facing_fist:"] = "\U0001f91b",
[":left_fist:"] = "\U0001f91b",
[":left_facing_fist_tone1:"] = "\U0001f91b\U0001f3fb",
[":left_fist_tone1:"] = "\U0001f91b\U0001f3fb",
[":left_facing_fist::skin-tone-1:"] = "\U0001f91b\U0001f3fb",
[":left_fist::skin-tone-1:"] = "\U0001f91b\U0001f3fb",
[":left_facing_fist_tone2:"] = "\U0001f91b\U0001f3fc",
[":left_fist_tone2:"] = "\U0001f91b\U0001f3fc",
[":left_facing_fist::skin-tone-2:"] = "\U0001f91b\U0001f3fc",
[":left_fist::skin-tone-2:"] = "\U0001f91b\U0001f3fc",
[":left_facing_fist_tone3:"] = "\U0001f91b\U0001f3fd",
[":left_fist_tone3:"] = "\U0001f91b\U0001f3fd",
[":left_facing_fist::skin-tone-3:"] = "\U0001f91b\U0001f3fd",
[":left_fist::skin-tone-3:"] = "\U0001f91b\U0001f3fd",
[":left_facing_fist_tone4:"] = "\U0001f91b\U0001f3fe",
[":left_fist_tone4:"] = "\U0001f91b\U0001f3fe",
[":left_facing_fist::skin-tone-4:"] = "\U0001f91b\U0001f3fe",
[":left_fist::skin-tone-4:"] = "\U0001f91b\U0001f3fe",
[":left_facing_fist_tone5:"] = "\U0001f91b\U0001f3ff",
[":left_fist_tone5:"] = "\U0001f91b\U0001f3ff",
[":left_facing_fist::skin-tone-5:"] = "\U0001f91b\U0001f3ff",
[":left_fist::skin-tone-5:"] = "\U0001f91b\U0001f3ff",
[":left_luggage:"] = "\U0001f6c5",
[":left_right_arrow:"] = "\u2194\ufe0f",
[":leftwards_arrow_with_hook:"] = "\u21a9\ufe0f",
[":leg:"] = "\U0001f9b5",
[":leg_tone1:"] = "\U0001f9b5\U0001f3fb",
[":leg_light_skin_tone:"] = "\U0001f9b5\U0001f3fb",
[":leg::skin-tone-1:"] = "\U0001f9b5\U0001f3fb",
[":leg_tone2:"] = "\U0001f9b5\U0001f3fc",
[":leg_medium_light_skin_tone:"] = "\U0001f9b5\U0001f3fc",
[":leg::skin-tone-2:"] = "\U0001f9b5\U0001f3fc",
[":leg_tone3:"] = "\U0001f9b5\U0001f3fd",
[":leg_medium_skin_tone:"] = "\U0001f9b5\U0001f3fd",
[":leg::skin-tone-3:"] = "\U0001f9b5\U0001f3fd",
[":leg_tone4:"] = "\U0001f9b5\U0001f3fe",
[":leg_medium_dark_skin_tone:"] = "\U0001f9b5\U0001f3fe",
[":leg::skin-tone-4:"] = "\U0001f9b5\U0001f3fe",
[":leg_tone5:"] = "\U0001f9b5\U0001f3ff",
[":leg_dark_skin_tone:"] = "\U0001f9b5\U0001f3ff",
[":leg::skin-tone-5:"] = "\U0001f9b5\U0001f3ff",
[":lemon:"] = "\U0001f34b",
[":leo:"] = "\u264c",
[":leopard:"] = "\U0001f406",
[":level_slider:"] = "\U0001f39a\ufe0f",
[":levitate:"] = "\U0001f574\ufe0f",
[":man_in_business_suit_levitating:"] = "\U0001f574\ufe0f",
[":levitate_tone1:"] = "\U0001f574\U0001f3fb",
[":man_in_business_suit_levitating_tone1:"] = "\U0001f574\U0001f3fb",
[":man_in_business_suit_levitating_light_skin_tone:"] = "\U0001f574\U0001f3fb",
[":levitate::skin-tone-1:"] = "\U0001f574\U0001f3fb",
[":man_in_business_suit_levitating::skin-tone-1:"] = "\U0001f574\U0001f3fb",
[":levitate_tone2:"] = "\U0001f574\U0001f3fc",
[":man_in_business_suit_levitating_tone2:"] = "\U0001f574\U0001f3fc",
[":man_in_business_suit_levitating_medium_light_skin_tone:"] = "\U0001f574\U0001f3fc",
[":levitate::skin-tone-2:"] = "\U0001f574\U0001f3fc",
[":man_in_business_suit_levitating::skin-tone-2:"] = "\U0001f574\U0001f3fc",
[":levitate_tone3:"] = "\U0001f574\U0001f3fd",
[":man_in_business_suit_levitating_tone3:"] = "\U0001f574\U0001f3fd",
[":man_in_business_suit_levitating_medium_skin_tone:"] = "\U0001f574\U0001f3fd",
[":levitate::skin-tone-3:"] = "\U0001f574\U0001f3fd",
[":man_in_business_suit_levitating::skin-tone-3:"] = "\U0001f574\U0001f3fd",
[":levitate_tone4:"] = "\U0001f574\U0001f3fe",
[":man_in_business_suit_levitating_tone4:"] = "\U0001f574\U0001f3fe",
[":man_in_business_suit_levitating_medium_dark_skin_tone:"] = "\U0001f574\U0001f3fe",
[":levitate::skin-tone-4:"] = "\U0001f574\U0001f3fe",
[":man_in_business_suit_levitating::skin-tone-4:"] = "\U0001f574\U0001f3fe",
[":levitate_tone5:"] = "\U0001f574\U0001f3ff",
[":man_in_business_suit_levitating_tone5:"] = "\U0001f574\U0001f3ff",
[":man_in_business_suit_levitating_dark_skin_tone:"] = "\U0001f574\U0001f3ff",
[":levitate::skin-tone-5:"] = "\U0001f574\U0001f3ff",
[":man_in_business_suit_levitating::skin-tone-5:"] = "\U0001f574\U0001f3ff",
[":libra:"] = "\u264e",
[":light_rail:"] = "\U0001f688",
[":link:"] = "\U0001f517",
[":lion_face:"] = "\U0001f981",
[":lion:"] = "\U0001f981",
[":lips:"] = "\U0001f444",
[":lipstick:"] = "\U0001f484",
[":lizard:"] = "\U0001f98e",
[":llama:"] = "\U0001f999",
[":lobster:"] = "\U0001f99e",
[":lock:"] = "\U0001f512",
[":lock_with_ink_pen:"] = "\U0001f50f",
[":lollipop:"] = "\U0001f36d",
[":long_drum:"] = "\U0001fa98",
[":loop:"] = "\u27bf",
[":loud_sound:"] = "\U0001f50a",
[":loudspeaker:"] = "\U0001f4e2",
[":love_hotel:"] = "\U0001f3e9",
[":love_letter:"] = "\U0001f48c",
[":love_you_gesture:"] = "\U0001f91f",
[":love_you_gesture_tone1:"] = "\U0001f91f\U0001f3fb",
[":love_you_gesture_light_skin_tone:"] = "\U0001f91f\U0001f3fb",
[":love_you_gesture::skin-tone-1:"] = "\U0001f91f\U0001f3fb",
[":love_you_gesture_tone2:"] = "\U0001f91f\U0001f3fc",
[":love_you_gesture_medium_light_skin_tone:"] = "\U0001f91f\U0001f3fc",
[":love_you_gesture::skin-tone-2:"] = "\U0001f91f\U0001f3fc",
[":love_you_gesture_tone3:"] = "\U0001f91f\U0001f3fd",
[":love_you_gesture_medium_skin_tone:"] = "\U0001f91f\U0001f3fd",
[":love_you_gesture::skin-tone-3:"] = "\U0001f91f\U0001f3fd",
[":love_you_gesture_tone4:"] = "\U0001f91f\U0001f3fe",
[":love_you_gesture_medium_dark_skin_tone:"] = "\U0001f91f\U0001f3fe",
[":love_you_gesture::skin-tone-4:"] = "\U0001f91f\U0001f3fe",
[":love_you_gesture_tone5:"] = "\U0001f91f\U0001f3ff",
[":love_you_gesture_dark_skin_tone:"] = "\U0001f91f\U0001f3ff",
[":love_you_gesture::skin-tone-5:"] = "\U0001f91f\U0001f3ff",
[":low_brightness:"] = "\U0001f505",
[":luggage:"] = "\U0001f9f3",
[":lungs:"] = "\U0001fac1",
[":lying_face:"] = "\U0001f925",
[":liar:"] = "\U0001f925",
[":m:"] = "\u24c2\ufe0f",
[":mag:"] = "\U0001f50d",
[":mag_right:"] = "\U0001f50e",
[":mage:"] = "\U0001f9d9",
[":mage_tone1:"] = "\U0001f9d9\U0001f3fb",
[":mage_light_skin_tone:"] = "\U0001f9d9\U0001f3fb",
[":mage::skin-tone-1:"] = "\U0001f9d9\U0001f3fb",
[":mage_tone2:"] = "\U0001f9d9\U0001f3fc",
[":mage_medium_light_skin_tone:"] = "\U0001f9d9\U0001f3fc",
[":mage::skin-tone-2:"] = "\U0001f9d9\U0001f3fc",
[":mage_tone3:"] = "\U0001f9d9\U0001f3fd",
[":mage_medium_skin_tone:"] = "\U0001f9d9\U0001f3fd",
[":mage::skin-tone-3:"] = "\U0001f9d9\U0001f3fd",
[":mage_tone4:"] = "\U0001f9d9\U0001f3fe",
[":mage_medium_dark_skin_tone:"] = "\U0001f9d9\U0001f3fe",
[":mage::skin-tone-4:"] = "\U0001f9d9\U0001f3fe",
[":mage_tone5:"] = "\U0001f9d9\U0001f3ff",
[":mage_dark_skin_tone:"] = "\U0001f9d9\U0001f3ff",
[":mage::skin-tone-5:"] = "\U0001f9d9\U0001f3ff",
[":magic_wand:"] = "\U0001fa84",
[":magnet:"] = "\U0001f9f2",
[":mahjong:"] = "\U0001f004",
[":mailbox:"] = "\U0001f4eb",
[":mailbox_closed:"] = "\U0001f4ea",
[":mailbox_with_mail:"] = "\U0001f4ec",
[":mailbox_with_no_mail:"] = "\U0001f4ed",
[":male_sign:"] = "\u2642\ufe0f",
[":mammoth:"] = "\U0001f9a3",
[":man:"] = "\U0001f468",
[":man_artist:"] = "\U0001f468\u200d\U0001f3a8",
[":man_artist_tone1:"] = "\U0001f468\U0001f3fb\u200d\U0001f3a8",
[":man_artist_light_skin_tone:"] = "\U0001f468\U0001f3fb\u200d\U0001f3a8",
[":man_artist::skin-tone-1:"] = "\U0001f468\U0001f3fb\u200d\U0001f3a8",
[":man_artist_tone2:"] = "\U0001f468\U0001f3fc\u200d\U0001f3a8",
[":man_artist_medium_light_skin_tone:"] = "\U0001f468\U0001f3fc\u200d\U0001f3a8",
[":man_artist::skin-tone-2:"] = "\U0001f468\U0001f3fc\u200d\U0001f3a8",
[":man_artist_tone3:"] = "\U0001f468\U0001f3fd\u200d\U0001f3a8",
[":man_artist_medium_skin_tone:"] = "\U0001f468\U0001f3fd\u200d\U0001f3a8",
[":man_artist::skin-tone-3:"] = "\U0001f468\U0001f3fd\u200d\U0001f3a8",
[":man_artist_tone4:"] = "\U0001f468\U0001f3fe\u200d\U0001f3a8",
[":man_artist_medium_dark_skin_tone:"] = "\U0001f468\U0001f3fe\u200d\U0001f3a8",
[":man_artist::skin-tone-4:"] = "\U0001f468\U0001f3fe\u200d\U0001f3a8",
[":man_artist_tone5:"] = "\U0001f468\U0001f3ff\u200d\U0001f3a8",
[":man_artist_dark_skin_tone:"] = "\U0001f468\U0001f3ff\u200d\U0001f3a8",
[":man_artist::skin-tone-5:"] = "\U0001f468\U0001f3ff\u200d\U0001f3a8",
[":man_astronaut:"] = "\U0001f468\u200d\U0001f680",
[":man_astronaut_tone1:"] = "\U0001f468\U0001f3fb\u200d\U0001f680",
[":man_astronaut_light_skin_tone:"] = "\U0001f468\U0001f3fb\u200d\U0001f680",
[":man_astronaut::skin-tone-1:"] = "\U0001f468\U0001f3fb\u200d\U0001f680",
[":man_astronaut_tone2:"] = "\U0001f468\U0001f3fc\u200d\U0001f680",
[":man_astronaut_medium_light_skin_tone:"] = "\U0001f468\U0001f3fc\u200d\U0001f680",
[":man_astronaut::skin-tone-2:"] = "\U0001f468\U0001f3fc\u200d\U0001f680",
[":man_astronaut_tone3:"] = "\U0001f468\U0001f3fd\u200d\U0001f680",
[":man_astronaut_medium_skin_tone:"] = "\U0001f468\U0001f3fd\u200d\U0001f680",
[":man_astronaut::skin-tone-3:"] = "\U0001f468\U0001f3fd\u200d\U0001f680",
[":man_astronaut_tone4:"] = "\U0001f468\U0001f3fe\u200d\U0001f680",
[":man_astronaut_medium_dark_skin_tone:"] = "\U0001f468\U0001f3fe\u200d\U0001f680",
[":man_astronaut::skin-tone-4:"] = "\U0001f468\U0001f3fe\u200d\U0001f680",
[":man_astronaut_tone5:"] = "\U0001f468\U0001f3ff\u200d\U0001f680",
[":man_astronaut_dark_skin_tone:"] = "\U0001f468\U0001f3ff\u200d\U0001f680",
[":man_astronaut::skin-tone-5:"] = "\U0001f468\U0001f3ff\u200d\U0001f680",
[":man_bald:"] = "\U0001f468\u200d\U0001f9b2",
[":man_bald_tone1:"] = "\U0001f468\U0001f3fb\u200d\U0001f9b2",
[":man_bald_light_skin_tone:"] = "\U0001f468\U0001f3fb\u200d\U0001f9b2",
[":man_bald::skin-tone-1:"] = "\U0001f468\U0001f3fb\u200d\U0001f9b2",
[":man_bald_tone2:"] = "\U0001f468\U0001f3fc\u200d\U0001f9b2",
[":man_bald_medium_light_skin_tone:"] = "\U0001f468\U0001f3fc\u200d\U0001f9b2",
[":man_bald::skin-tone-2:"] = "\U0001f468\U0001f3fc\u200d\U0001f9b2",
[":man_bald_tone3:"] = "\U0001f468\U0001f3fd\u200d\U0001f9b2",
[":man_bald_medium_skin_tone:"] = "\U0001f468\U0001f3fd\u200d\U0001f9b2",
[":man_bald::skin-tone-3:"] = "\U0001f468\U0001f3fd\u200d\U0001f9b2",
[":man_bald_tone4:"] = "\U0001f468\U0001f3fe\u200d\U0001f9b2",
[":man_bald_medium_dark_skin_tone:"] = "\U0001f468\U0001f3fe\u200d\U0001f9b2",
[":man_bald::skin-tone-4:"] = "\U0001f468\U0001f3fe\u200d\U0001f9b2",
[":man_bald_tone5:"] = "\U0001f468\U0001f3ff\u200d\U0001f9b2",
[":man_bald_dark_skin_tone:"] = "\U0001f468\U0001f3ff\u200d\U0001f9b2",
[":man_bald::skin-tone-5:"] = "\U0001f468\U0001f3ff\u200d\U0001f9b2",
[":man_biking:"] = "\U0001f6b4\u200d\u2642\ufe0f",
[":man_biking_tone1:"] = "\U0001f6b4\U0001f3fb\u200d\u2642\ufe0f",
[":man_biking_light_skin_tone:"] = "\U0001f6b4\U0001f3fb\u200d\u2642\ufe0f",
[":man_biking::skin-tone-1:"] = "\U0001f6b4\U0001f3fb\u200d\u2642\ufe0f",
[":man_biking_tone2:"] = "\U0001f6b4\U0001f3fc\u200d\u2642\ufe0f",
[":man_biking_medium_light_skin_tone:"] = "\U0001f6b4\U0001f3fc\u200d\u2642\ufe0f",
[":man_biking::skin-tone-2:"] = "\U0001f6b4\U0001f3fc\u200d\u2642\ufe0f",
[":man_biking_tone3:"] = "\U0001f6b4\U0001f3fd\u200d\u2642\ufe0f",
[":man_biking_medium_skin_tone:"] = "\U0001f6b4\U0001f3fd\u200d\u2642\ufe0f",
[":man_biking::skin-tone-3:"] = "\U0001f6b4\U0001f3fd\u200d\u2642\ufe0f",
[":man_biking_tone4:"] = "\U0001f6b4\U0001f3fe\u200d\u2642\ufe0f",
[":man_biking_medium_dark_skin_tone:"] = "\U0001f6b4\U0001f3fe\u200d\u2642\ufe0f",
[":man_biking::skin-tone-4:"] = "\U0001f6b4\U0001f3fe\u200d\u2642\ufe0f",
[":man_biking_tone5:"] = "\U0001f6b4\U0001f3ff\u200d\u2642\ufe0f",
[":man_biking_dark_skin_tone:"] = "\U0001f6b4\U0001f3ff\u200d\u2642\ufe0f",
[":man_biking::skin-tone-5:"] = "\U0001f6b4\U0001f3ff\u200d\u2642\ufe0f",
[":man_bouncing_ball:"] = "\u26f9\ufe0f\u200d\u2642\ufe0f",
[":man_bouncing_ball_tone1:"] = "\u26f9\U0001f3fb\u200d\u2642\ufe0f",
[":man_bouncing_ball_light_skin_tone:"] = "\u26f9\U0001f3fb\u200d\u2642\ufe0f",
[":man_bouncing_ball::skin-tone-1:"] = "\u26f9\U0001f3fb\u200d\u2642\ufe0f",
[":man_bouncing_ball_tone2:"] = "\u26f9\U0001f3fc\u200d\u2642\ufe0f",
[":man_bouncing_ball_medium_light_skin_tone:"] = "\u26f9\U0001f3fc\u200d\u2642\ufe0f",
[":man_bouncing_ball::skin-tone-2:"] = "\u26f9\U0001f3fc\u200d\u2642\ufe0f",
[":man_bouncing_ball_tone3:"] = "\u26f9\U0001f3fd\u200d\u2642\ufe0f",
[":man_bouncing_ball_medium_skin_tone:"] = "\u26f9\U0001f3fd\u200d\u2642\ufe0f",
[":man_bouncing_ball::skin-tone-3:"] = "\u26f9\U0001f3fd\u200d\u2642\ufe0f",
[":man_bouncing_ball_tone4:"] = "\u26f9\U0001f3fe\u200d\u2642\ufe0f",
[":man_bouncing_ball_medium_dark_skin_tone:"] = "\u26f9\U0001f3fe\u200d\u2642\ufe0f",
[":man_bouncing_ball::skin-tone-4:"] = "\u26f9\U0001f3fe\u200d\u2642\ufe0f",
[":man_bouncing_ball_tone5:"] = "\u26f9\U0001f3ff\u200d\u2642\ufe0f",
[":man_bouncing_ball_dark_skin_tone:"] = "\u26f9\U0001f3ff\u200d\u2642\ufe0f",
[":man_bouncing_ball::skin-tone-5:"] = "\u26f9\U0001f3ff\u200d\u2642\ufe0f",
[":man_bowing:"] = "\U0001f647\u200d\u2642\ufe0f",
[":man_bowing_tone1:"] = "\U0001f647\U0001f3fb\u200d\u2642\ufe0f",
[":man_bowing_light_skin_tone:"] = "\U0001f647\U0001f3fb\u200d\u2642\ufe0f",
[":man_bowing::skin-tone-1:"] = "\U0001f647\U0001f3fb\u200d\u2642\ufe0f",
[":man_bowing_tone2:"] = "\U0001f647\U0001f3fc\u200d\u2642\ufe0f",
[":man_bowing_medium_light_skin_tone:"] = "\U0001f647\U0001f3fc\u200d\u2642\ufe0f",
[":man_bowing::skin-tone-2:"] = "\U0001f647\U0001f3fc\u200d\u2642\ufe0f",
[":man_bowing_tone3:"] = "\U0001f647\U0001f3fd\u200d\u2642\ufe0f",
[":man_bowing_medium_skin_tone:"] = "\U0001f647\U0001f3fd\u200d\u2642\ufe0f",
[":man_bowing::skin-tone-3:"] = "\U0001f647\U0001f3fd\u200d\u2642\ufe0f",
[":man_bowing_tone4:"] = "\U0001f647\U0001f3fe\u200d\u2642\ufe0f",
[":man_bowing_medium_dark_skin_tone:"] = "\U0001f647\U0001f3fe\u200d\u2642\ufe0f",
[":man_bowing::skin-tone-4:"] = "\U0001f647\U0001f3fe\u200d\u2642\ufe0f",
[":man_bowing_tone5:"] = "\U0001f647\U0001f3ff\u200d\u2642\ufe0f",
[":man_bowing_dark_skin_tone:"] = "\U0001f647\U0001f3ff\u200d\u2642\ufe0f",
[":man_bowing::skin-tone-5:"] = "\U0001f647\U0001f3ff\u200d\u2642\ufe0f",
[":man_cartwheeling:"] = "\U0001f938\u200d\u2642\ufe0f",
[":man_cartwheeling_tone1:"] = "\U0001f938\U0001f3fb\u200d\u2642\ufe0f",
[":man_cartwheeling_light_skin_tone:"] = "\U0001f938\U0001f3fb\u200d\u2642\ufe0f",
[":man_cartwheeling::skin-tone-1:"] = "\U0001f938\U0001f3fb\u200d\u2642\ufe0f",
[":man_cartwheeling_tone2:"] = "\U0001f938\U0001f3fc\u200d\u2642\ufe0f",
[":man_cartwheeling_medium_light_skin_tone:"] = "\U0001f938\U0001f3fc\u200d\u2642\ufe0f",
[":man_cartwheeling::skin-tone-2:"] = "\U0001f938\U0001f3fc\u200d\u2642\ufe0f",
[":man_cartwheeling_tone3:"] = "\U0001f938\U0001f3fd\u200d\u2642\ufe0f",
[":man_cartwheeling_medium_skin_tone:"] = "\U0001f938\U0001f3fd\u200d\u2642\ufe0f",
[":man_cartwheeling::skin-tone-3:"] = "\U0001f938\U0001f3fd\u200d\u2642\ufe0f",
[":man_cartwheeling_tone4:"] = "\U0001f938\U0001f3fe\u200d\u2642\ufe0f",
[":man_cartwheeling_medium_dark_skin_tone:"] = "\U0001f938\U0001f3fe\u200d\u2642\ufe0f",
[":man_cartwheeling::skin-tone-4:"] = "\U0001f938\U0001f3fe\u200d\u2642\ufe0f",
[":man_cartwheeling_tone5:"] = "\U0001f938\U0001f3ff\u200d\u2642\ufe0f",
[":man_cartwheeling_dark_skin_tone:"] = "\U0001f938\U0001f3ff\u200d\u2642\ufe0f",
[":man_cartwheeling::skin-tone-5:"] = "\U0001f938\U0001f3ff\u200d\u2642\ufe0f",
[":man_climbing:"] = "\U0001f9d7\u200d\u2642\ufe0f",
[":man_climbing_tone1:"] = "\U0001f9d7\U0001f3fb\u200d\u2642\ufe0f",
[":man_climbing_light_skin_tone:"] = "\U0001f9d7\U0001f3fb\u200d\u2642\ufe0f",
[":man_climbing::skin-tone-1:"] = "\U0001f9d7\U0001f3fb\u200d\u2642\ufe0f",
[":man_climbing_tone2:"] = "\U0001f9d7\U0001f3fc\u200d\u2642\ufe0f",
[":man_climbing_medium_light_skin_tone:"] = "\U0001f9d7\U0001f3fc\u200d\u2642\ufe0f",
[":man_climbing::skin-tone-2:"] = "\U0001f9d7\U0001f3fc\u200d\u2642\ufe0f",
[":man_climbing_tone3:"] = "\U0001f9d7\U0001f3fd\u200d\u2642\ufe0f",
[":man_climbing_medium_skin_tone:"] = "\U0001f9d7\U0001f3fd\u200d\u2642\ufe0f",
[":man_climbing::skin-tone-3:"] = "\U0001f9d7\U0001f3fd\u200d\u2642\ufe0f",
[":man_climbing_tone4:"] = "\U0001f9d7\U0001f3fe\u200d\u2642\ufe0f",
[":man_climbing_medium_dark_skin_tone:"] = "\U0001f9d7\U0001f3fe\u200d\u2642\ufe0f",
[":man_climbing::skin-tone-4:"] = "\U0001f9d7\U0001f3fe\u200d\u2642\ufe0f",
[":man_climbing_tone5:"] = "\U0001f9d7\U0001f3ff\u200d\u2642\ufe0f",
[":man_climbing_dark_skin_tone:"] = "\U0001f9d7\U0001f3ff\u200d\u2642\ufe0f",
[":man_climbing::skin-tone-5:"] = "\U0001f9d7\U0001f3ff\u200d\u2642\ufe0f",
[":man_construction_worker:"] = "\U0001f477\u200d\u2642\ufe0f",
[":man_construction_worker_tone1:"] = "\U0001f477\U0001f3fb\u200d\u2642\ufe0f",
[":man_construction_worker_light_skin_tone:"] = "\U0001f477\U0001f3fb\u200d\u2642\ufe0f",
[":man_construction_worker::skin-tone-1:"] = "\U0001f477\U0001f3fb\u200d\u2642\ufe0f",
[":man_construction_worker_tone2:"] = "\U0001f477\U0001f3fc\u200d\u2642\ufe0f",
[":man_construction_worker_medium_light_skin_tone:"] = "\U0001f477\U0001f3fc\u200d\u2642\ufe0f",
[":man_construction_worker::skin-tone-2:"] = "\U0001f477\U0001f3fc\u200d\u2642\ufe0f",
[":man_construction_worker_tone3:"] = "\U0001f477\U0001f3fd\u200d\u2642\ufe0f",
[":man_construction_worker_medium_skin_tone:"] = "\U0001f477\U0001f3fd\u200d\u2642\ufe0f",
[":man_construction_worker::skin-tone-3:"] = "\U0001f477\U0001f3fd\u200d\u2642\ufe0f",
[":man_construction_worker_tone4:"] = "\U0001f477\U0001f3fe\u200d\u2642\ufe0f",
[":man_construction_worker_medium_dark_skin_tone:"] = "\U0001f477\U0001f3fe\u200d\u2642\ufe0f",
[":man_construction_worker::skin-tone-4:"] = "\U0001f477\U0001f3fe\u200d\u2642\ufe0f",
[":man_construction_worker_tone5:"] = "\U0001f477\U0001f3ff\u200d\u2642\ufe0f",
[":man_construction_worker_dark_skin_tone:"] = "\U0001f477\U0001f3ff\u200d\u2642\ufe0f",
[":man_construction_worker::skin-tone-5:"] = "\U0001f477\U0001f3ff\u200d\u2642\ufe0f",
[":man_cook:"] = "\U0001f468\u200d\U0001f373",
[":man_cook_tone1:"] = "\U0001f468\U0001f3fb\u200d\U0001f373",
[":man_cook_light_skin_tone:"] = "\U0001f468\U0001f3fb\u200d\U0001f373",
[":man_cook::skin-tone-1:"] = "\U0001f468\U0001f3fb\u200d\U0001f373",
[":man_cook_tone2:"] = "\U0001f468\U0001f3fc\u200d\U0001f373",
[":man_cook_medium_light_skin_tone:"] = "\U0001f468\U0001f3fc\u200d\U0001f373",
[":man_cook::skin-tone-2:"] = "\U0001f468\U0001f3fc\u200d\U0001f373",
[":man_cook_tone3:"] = "\U0001f468\U0001f3fd\u200d\U0001f373",
[":man_cook_medium_skin_tone:"] = "\U0001f468\U0001f3fd\u200d\U0001f373",
[":man_cook::skin-tone-3:"] = "\U0001f468\U0001f3fd\u200d\U0001f373",
[":man_cook_tone4:"] = "\U0001f468\U0001f3fe\u200d\U0001f373",
[":man_cook_medium_dark_skin_tone:"] = "\U0001f468\U0001f3fe\u200d\U0001f373",
[":man_cook::skin-tone-4:"] = "\U0001f468\U0001f3fe\u200d\U0001f373",
[":man_cook_tone5:"] = "\U0001f468\U0001f3ff\u200d\U0001f373",
[":man_cook_dark_skin_tone:"] = "\U0001f468\U0001f3ff\u200d\U0001f373",
[":man_cook::skin-tone-5:"] = "\U0001f468\U0001f3ff\u200d\U0001f373",
[":man_curly_haired:"] = "\U0001f468\u200d\U0001f9b1",
[":man_curly_haired_tone1:"] = "\U0001f468\U0001f3fb\u200d\U0001f9b1",
[":man_curly_haired_light_skin_tone:"] = "\U0001f468\U0001f3fb\u200d\U0001f9b1",
[":man_curly_haired::skin-tone-1:"] = "\U0001f468\U0001f3fb\u200d\U0001f9b1",
[":man_curly_haired_tone2:"] = "\U0001f468\U0001f3fc\u200d\U0001f9b1",
[":man_curly_haired_medium_light_skin_tone:"] = "\U0001f468\U0001f3fc\u200d\U0001f9b1",
[":man_curly_haired::skin-tone-2:"] = "\U0001f468\U0001f3fc\u200d\U0001f9b1",
[":man_curly_haired_tone3:"] = "\U0001f468\U0001f3fd\u200d\U0001f9b1",
[":man_curly_haired_medium_skin_tone:"] = "\U0001f468\U0001f3fd\u200d\U0001f9b1",
[":man_curly_haired::skin-tone-3:"] = "\U0001f468\U0001f3fd\u200d\U0001f9b1",
[":man_curly_haired_tone4:"] = "\U0001f468\U0001f3fe\u200d\U0001f9b1",
[":man_curly_haired_medium_dark_skin_tone:"] = "\U0001f468\U0001f3fe\u200d\U0001f9b1",
[":man_curly_haired::skin-tone-4:"] = "\U0001f468\U0001f3fe\u200d\U0001f9b1",
[":man_curly_haired_tone5:"] = "\U0001f468\U0001f3ff\u200d\U0001f9b1",
[":man_curly_haired_dark_skin_tone:"] = "\U0001f468\U0001f3ff\u200d\U0001f9b1",
[":man_curly_haired::skin-tone-5:"] = "\U0001f468\U0001f3ff\u200d\U0001f9b1",
[":man_dancing:"] = "\U0001f57a",
[":male_dancer:"] = "\U0001f57a",
[":man_dancing_tone1:"] = "\U0001f57a\U0001f3fb",
[":male_dancer_tone1:"] = "\U0001f57a\U0001f3fb",
[":man_dancing::skin-tone-1:"] = "\U0001f57a\U0001f3fb",
[":male_dancer::skin-tone-1:"] = "\U0001f57a\U0001f3fb",
[":man_dancing_tone2:"] = "\U0001f57a\U0001f3fc",
[":male_dancer_tone2:"] = "\U0001f57a\U0001f3fc",
[":man_dancing::skin-tone-2:"] = "\U0001f57a\U0001f3fc",
[":male_dancer::skin-tone-2:"] = "\U0001f57a\U0001f3fc",
[":man_dancing_tone3:"] = "\U0001f57a\U0001f3fd",
[":male_dancer_tone3:"] = "\U0001f57a\U0001f3fd",
[":man_dancing::skin-tone-3:"] = "\U0001f57a\U0001f3fd",
[":male_dancer::skin-tone-3:"] = "\U0001f57a\U0001f3fd",
[":man_dancing_tone4:"] = "\U0001f57a\U0001f3fe",
[":male_dancer_tone4:"] = "\U0001f57a\U0001f3fe",
[":man_dancing::skin-tone-4:"] = "\U0001f57a\U0001f3fe",
[":male_dancer::skin-tone-4:"] = "\U0001f57a\U0001f3fe",
[":man_dancing_tone5:"] = "\U0001f57a\U0001f3ff",
[":male_dancer_tone5:"] = "\U0001f57a\U0001f3ff",
[":man_dancing::skin-tone-5:"] = "\U0001f57a\U0001f3ff",
[":male_dancer::skin-tone-5:"] = "\U0001f57a\U0001f3ff",
[":man_detective:"] = "\U0001f575\ufe0f\u200d\u2642\ufe0f",
[":man_detective_tone1:"] = "\U0001f575\U0001f3fb\u200d\u2642\ufe0f",
[":man_detective_light_skin_tone:"] = "\U0001f575\U0001f3fb\u200d\u2642\ufe0f",
[":man_detective::skin-tone-1:"] = "\U0001f575\U0001f3fb\u200d\u2642\ufe0f",
[":man_detective_tone2:"] = "\U0001f575\U0001f3fc\u200d\u2642\ufe0f",
[":man_detective_medium_light_skin_tone:"] = "\U0001f575\U0001f3fc\u200d\u2642\ufe0f",
[":man_detective::skin-tone-2:"] = "\U0001f575\U0001f3fc\u200d\u2642\ufe0f",
[":man_detective_tone3:"] = "\U0001f575\U0001f3fd\u200d\u2642\ufe0f",
[":man_detective_medium_skin_tone:"] = "\U0001f575\U0001f3fd\u200d\u2642\ufe0f",
[":man_detective::skin-tone-3:"] = "\U0001f575\U0001f3fd\u200d\u2642\ufe0f",
[":man_detective_tone4:"] = "\U0001f575\U0001f3fe\u200d\u2642\ufe0f",
[":man_detective_medium_dark_skin_tone:"] = "\U0001f575\U0001f3fe\u200d\u2642\ufe0f",
[":man_detective::skin-tone-4:"] = "\U0001f575\U0001f3fe\u200d\u2642\ufe0f",
[":man_detective_tone5:"] = "\U0001f575\U0001f3ff\u200d\u2642\ufe0f",
[":man_detective_dark_skin_tone:"] = "\U0001f575\U0001f3ff\u200d\u2642\ufe0f",
[":man_detective::skin-tone-5:"] = "\U0001f575\U0001f3ff\u200d\u2642\ufe0f",
[":man_elf:"] = "\U0001f9dd\u200d\u2642\ufe0f",
[":man_elf_tone1:"] = "\U0001f9dd\U0001f3fb\u200d\u2642\ufe0f",
[":man_elf_light_skin_tone:"] = "\U0001f9dd\U0001f3fb\u200d\u2642\ufe0f",
[":man_elf::skin-tone-1:"] = "\U0001f9dd\U0001f3fb\u200d\u2642\ufe0f",
[":man_elf_tone2:"] = "\U0001f9dd\U0001f3fc\u200d\u2642\ufe0f",
[":man_elf_medium_light_skin_tone:"] = "\U0001f9dd\U0001f3fc\u200d\u2642\ufe0f",
[":man_elf::skin-tone-2:"] = "\U0001f9dd\U0001f3fc\u200d\u2642\ufe0f",
[":man_elf_tone3:"] = "\U0001f9dd\U0001f3fd\u200d\u2642\ufe0f",
[":man_elf_medium_skin_tone:"] = "\U0001f9dd\U0001f3fd\u200d\u2642\ufe0f",
[":man_elf::skin-tone-3:"] = "\U0001f9dd\U0001f3fd\u200d\u2642\ufe0f",
[":man_elf_tone4:"] = "\U0001f9dd\U0001f3fe\u200d\u2642\ufe0f",
[":man_elf_medium_dark_skin_tone:"] = "\U0001f9dd\U0001f3fe\u200d\u2642\ufe0f",
[":man_elf::skin-tone-4:"] = "\U0001f9dd\U0001f3fe\u200d\u2642\ufe0f",
[":man_elf_tone5:"] = "\U0001f9dd\U0001f3ff\u200d\u2642\ufe0f",
[":man_elf_dark_skin_tone:"] = "\U0001f9dd\U0001f3ff\u200d\u2642\ufe0f",
[":man_elf::skin-tone-5:"] = "\U0001f9dd\U0001f3ff\u200d\u2642\ufe0f",
[":man_facepalming:"] = "\U0001f926\u200d\u2642\ufe0f",
[":man_facepalming_tone1:"] = "\U0001f926\U0001f3fb\u200d\u2642\ufe0f",
[":man_facepalming_light_skin_tone:"] = "\U0001f926\U0001f3fb\u200d\u2642\ufe0f",
[":man_facepalming::skin-tone-1:"] = "\U0001f926\U0001f3fb\u200d\u2642\ufe0f",
[":man_facepalming_tone2:"] = "\U0001f926\U0001f3fc\u200d\u2642\ufe0f",
[":man_facepalming_medium_light_skin_tone:"] = "\U0001f926\U0001f3fc\u200d\u2642\ufe0f",
[":man_facepalming::skin-tone-2:"] = "\U0001f926\U0001f3fc\u200d\u2642\ufe0f",
[":man_facepalming_tone3:"] = "\U0001f926\U0001f3fd\u200d\u2642\ufe0f",
[":man_facepalming_medium_skin_tone:"] = "\U0001f926\U0001f3fd\u200d\u2642\ufe0f",
[":man_facepalming::skin-tone-3:"] = "\U0001f926\U0001f3fd\u200d\u2642\ufe0f",
[":man_facepalming_tone4:"] = "\U0001f926\U0001f3fe\u200d\u2642\ufe0f",
[":man_facepalming_medium_dark_skin_tone:"] = "\U0001f926\U0001f3fe\u200d\u2642\ufe0f",
[":man_facepalming::skin-tone-4:"] = "\U0001f926\U0001f3fe\u200d\u2642\ufe0f",
[":man_facepalming_tone5:"] = "\U0001f926\U0001f3ff\u200d\u2642\ufe0f",
[":man_facepalming_dark_skin_tone:"] = "\U0001f926\U0001f3ff\u200d\u2642\ufe0f",
[":man_facepalming::skin-tone-5:"] = "\U0001f926\U0001f3ff\u200d\u2642\ufe0f",
[":man_factory_worker:"] = "\U0001f468\u200d\U0001f3ed",
[":man_factory_worker_tone1:"] = "\U0001f468\U0001f3fb\u200d\U0001f3ed",
[":man_factory_worker_light_skin_tone:"] = "\U0001f468\U0001f3fb\u200d\U0001f3ed",
[":man_factory_worker::skin-tone-1:"] = "\U0001f468\U0001f3fb\u200d\U0001f3ed",
[":man_factory_worker_tone2:"] = "\U0001f468\U0001f3fc\u200d\U0001f3ed",
[":man_factory_worker_medium_light_skin_tone:"] = "\U0001f468\U0001f3fc\u200d\U0001f3ed",
[":man_factory_worker::skin-tone-2:"] = "\U0001f468\U0001f3fc\u200d\U0001f3ed",
[":man_factory_worker_tone3:"] = "\U0001f468\U0001f3fd\u200d\U0001f3ed",
[":man_factory_worker_medium_skin_tone:"] = "\U0001f468\U0001f3fd\u200d\U0001f3ed",
[":man_factory_worker::skin-tone-3:"] = "\U0001f468\U0001f3fd\u200d\U0001f3ed",
[":man_factory_worker_tone4:"] = "\U0001f468\U0001f3fe\u200d\U0001f3ed",
[":man_factory_worker_medium_dark_skin_tone:"] = "\U0001f468\U0001f3fe\u200d\U0001f3ed",
[":man_factory_worker::skin-tone-4:"] = "\U0001f468\U0001f3fe\u200d\U0001f3ed",
[":man_factory_worker_tone5:"] = "\U0001f468\U0001f3ff\u200d\U0001f3ed",
[":man_factory_worker_dark_skin_tone:"] = "\U0001f468\U0001f3ff\u200d\U0001f3ed",
[":man_factory_worker::skin-tone-5:"] = "\U0001f468\U0001f3ff\u200d\U0001f3ed",
[":man_fairy:"] = "\U0001f9da\u200d\u2642\ufe0f",
[":man_fairy_tone1:"] = "\U0001f9da\U0001f3fb\u200d\u2642\ufe0f",
[":man_fairy_light_skin_tone:"] = "\U0001f9da\U0001f3fb\u200d\u2642\ufe0f",
[":man_fairy::skin-tone-1:"] = "\U0001f9da\U0001f3fb\u200d\u2642\ufe0f",
[":man_fairy_tone2:"] = "\U0001f9da\U0001f3fc\u200d\u2642\ufe0f",
[":man_fairy_medium_light_skin_tone:"] = "\U0001f9da\U0001f3fc\u200d\u2642\ufe0f",
[":man_fairy::skin-tone-2:"] = "\U0001f9da\U0001f3fc\u200d\u2642\ufe0f",
[":man_fairy_tone3:"] = "\U0001f9da\U0001f3fd\u200d\u2642\ufe0f",
[":man_fairy_medium_skin_tone:"] = "\U0001f9da\U0001f3fd\u200d\u2642\ufe0f",
[":man_fairy::skin-tone-3:"] = "\U0001f9da\U0001f3fd\u200d\u2642\ufe0f",
[":man_fairy_tone4:"] = "\U0001f9da\U0001f3fe\u200d\u2642\ufe0f",
[":man_fairy_medium_dark_skin_tone:"] = "\U0001f9da\U0001f3fe\u200d\u2642\ufe0f",
[":man_fairy::skin-tone-4:"] = "\U0001f9da\U0001f3fe\u200d\u2642\ufe0f",
[":man_fairy_tone5:"] = "\U0001f9da\U0001f3ff\u200d\u2642\ufe0f",
[":man_fairy_dark_skin_tone:"] = "\U0001f9da\U0001f3ff\u200d\u2642\ufe0f",
[":man_fairy::skin-tone-5:"] = "\U0001f9da\U0001f3ff\u200d\u2642\ufe0f",
[":man_farmer:"] = "\U0001f468\u200d\U0001f33e",
[":man_farmer_tone1:"] = "\U0001f468\U0001f3fb\u200d\U0001f33e",
[":man_farmer_light_skin_tone:"] = "\U0001f468\U0001f3fb\u200d\U0001f33e",
[":man_farmer::skin-tone-1:"] = "\U0001f468\U0001f3fb\u200d\U0001f33e",
[":man_farmer_tone2:"] = "\U0001f468\U0001f3fc\u200d\U0001f33e",
[":man_farmer_medium_light_skin_tone:"] = "\U0001f468\U0001f3fc\u200d\U0001f33e",
[":man_farmer::skin-tone-2:"] = "\U0001f468\U0001f3fc\u200d\U0001f33e",
[":man_farmer_tone3:"] = "\U0001f468\U0001f3fd\u200d\U0001f33e",
[":man_farmer_medium_skin_tone:"] = "\U0001f468\U0001f3fd\u200d\U0001f33e",
[":man_farmer::skin-tone-3:"] = "\U0001f468\U0001f3fd\u200d\U0001f33e",
[":man_farmer_tone4:"] = "\U0001f468\U0001f3fe\u200d\U0001f33e",
[":man_farmer_medium_dark_skin_tone:"] = "\U0001f468\U0001f3fe\u200d\U0001f33e",
[":man_farmer::skin-tone-4:"] = "\U0001f468\U0001f3fe\u200d\U0001f33e",
[":man_farmer_tone5:"] = "\U0001f468\U0001f3ff\u200d\U0001f33e",
[":man_farmer_dark_skin_tone:"] = "\U0001f468\U0001f3ff\u200d\U0001f33e",
[":man_farmer::skin-tone-5:"] = "\U0001f468\U0001f3ff\u200d\U0001f33e",
[":man_feeding_baby:"] = "\U0001f468\u200d\U0001f37c",
[":man_feeding_baby_tone1:"] = "\U0001f468\U0001f3fb\u200d\U0001f37c",
[":man_feeding_baby_light_skin_tone:"] = "\U0001f468\U0001f3fb\u200d\U0001f37c",
[":man_feeding_baby::skin-tone-1:"] = "\U0001f468\U0001f3fb\u200d\U0001f37c",
[":man_feeding_baby_tone2:"] = "\U0001f468\U0001f3fc\u200d\U0001f37c",
[":man_feeding_baby_medium_light_skin_tone:"] = "\U0001f468\U0001f3fc\u200d\U0001f37c",
[":man_feeding_baby::skin-tone-2:"] = "\U0001f468\U0001f3fc\u200d\U0001f37c",
[":man_feeding_baby_tone3:"] = "\U0001f468\U0001f3fd\u200d\U0001f37c",
[":man_feeding_baby_medium_skin_tone:"] = "\U0001f468\U0001f3fd\u200d\U0001f37c",
[":man_feeding_baby::skin-tone-3:"] = "\U0001f468\U0001f3fd\u200d\U0001f37c",
[":man_feeding_baby_tone4:"] = "\U0001f468\U0001f3fe\u200d\U0001f37c",
[":man_feeding_baby_medium_dark_skin_tone:"] = "\U0001f468\U0001f3fe\u200d\U0001f37c",
[":man_feeding_baby::skin-tone-4:"] = "\U0001f468\U0001f3fe\u200d\U0001f37c",
[":man_feeding_baby_tone5:"] = "\U0001f468\U0001f3ff\u200d\U0001f37c",
[":man_feeding_baby_dark_skin_tone:"] = "\U0001f468\U0001f3ff\u200d\U0001f37c",
[":man_feeding_baby::skin-tone-5:"] = "\U0001f468\U0001f3ff\u200d\U0001f37c",
[":man_firefighter:"] = "\U0001f468\u200d\U0001f692",
[":man_firefighter_tone1:"] = "\U0001f468\U0001f3fb\u200d\U0001f692",
[":man_firefighter_light_skin_tone:"] = "\U0001f468\U0001f3fb\u200d\U0001f692",
[":man_firefighter::skin-tone-1:"] = "\U0001f468\U0001f3fb\u200d\U0001f692",
[":man_firefighter_tone2:"] = "\U0001f468\U0001f3fc\u200d\U0001f692",
[":man_firefighter_medium_light_skin_tone:"] = "\U0001f468\U0001f3fc\u200d\U0001f692",
[":man_firefighter::skin-tone-2:"] = "\U0001f468\U0001f3fc\u200d\U0001f692",
[":man_firefighter_tone3:"] = "\U0001f468\U0001f3fd\u200d\U0001f692",
[":man_firefighter_medium_skin_tone:"] = "\U0001f468\U0001f3fd\u200d\U0001f692",
[":man_firefighter::skin-tone-3:"] = "\U0001f468\U0001f3fd\u200d\U0001f692",
[":man_firefighter_tone4:"] = "\U0001f468\U0001f3fe\u200d\U0001f692",
[":man_firefighter_medium_dark_skin_tone:"] = "\U0001f468\U0001f3fe\u200d\U0001f692",
[":man_firefighter::skin-tone-4:"] = "\U0001f468\U0001f3fe\u200d\U0001f692",
[":man_firefighter_tone5:"] = "\U0001f468\U0001f3ff\u200d\U0001f692",
[":man_firefighter_dark_skin_tone:"] = "\U0001f468\U0001f3ff\u200d\U0001f692",
[":man_firefighter::skin-tone-5:"] = "\U0001f468\U0001f3ff\u200d\U0001f692",
[":man_frowning:"] = "\U0001f64d\u200d\u2642\ufe0f",
[":man_frowning_tone1:"] = "\U0001f64d\U0001f3fb\u200d\u2642\ufe0f",
[":man_frowning_light_skin_tone:"] = "\U0001f64d\U0001f3fb\u200d\u2642\ufe0f",
[":man_frowning::skin-tone-1:"] = "\U0001f64d\U0001f3fb\u200d\u2642\ufe0f",
[":man_frowning_tone2:"] = "\U0001f64d\U0001f3fc\u200d\u2642\ufe0f",
[":man_frowning_medium_light_skin_tone:"] = "\U0001f64d\U0001f3fc\u200d\u2642\ufe0f",
[":man_frowning::skin-tone-2:"] = "\U0001f64d\U0001f3fc\u200d\u2642\ufe0f",
[":man_frowning_tone3:"] = "\U0001f64d\U0001f3fd\u200d\u2642\ufe0f",
[":man_frowning_medium_skin_tone:"] = "\U0001f64d\U0001f3fd\u200d\u2642\ufe0f",
[":man_frowning::skin-tone-3:"] = "\U0001f64d\U0001f3fd\u200d\u2642\ufe0f",
[":man_frowning_tone4:"] = "\U0001f64d\U0001f3fe\u200d\u2642\ufe0f",
[":man_frowning_medium_dark_skin_tone:"] = "\U0001f64d\U0001f3fe\u200d\u2642\ufe0f",
[":man_frowning::skin-tone-4:"] = "\U0001f64d\U0001f3fe\u200d\u2642\ufe0f",
[":man_frowning_tone5:"] = "\U0001f64d\U0001f3ff\u200d\u2642\ufe0f",
[":man_frowning_dark_skin_tone:"] = "\U0001f64d\U0001f3ff\u200d\u2642\ufe0f",
[":man_frowning::skin-tone-5:"] = "\U0001f64d\U0001f3ff\u200d\u2642\ufe0f",
[":man_genie:"] = "\U0001f9de\u200d\u2642\ufe0f",
[":man_gesturing_no:"] = "\U0001f645\u200d\u2642\ufe0f",
[":man_gesturing_no_tone1:"] = "\U0001f645\U0001f3fb\u200d\u2642\ufe0f",
[":man_gesturing_no_light_skin_tone:"] = "\U0001f645\U0001f3fb\u200d\u2642\ufe0f",
[":man_gesturing_no::skin-tone-1:"] = "\U0001f645\U0001f3fb\u200d\u2642\ufe0f",
[":man_gesturing_no_tone2:"] = "\U0001f645\U0001f3fc\u200d\u2642\ufe0f",
[":man_gesturing_no_medium_light_skin_tone:"] = "\U0001f645\U0001f3fc\u200d\u2642\ufe0f",
[":man_gesturing_no::skin-tone-2:"] = "\U0001f645\U0001f3fc\u200d\u2642\ufe0f",
[":man_gesturing_no_tone3:"] = "\U0001f645\U0001f3fd\u200d\u2642\ufe0f",
[":man_gesturing_no_medium_skin_tone:"] = "\U0001f645\U0001f3fd\u200d\u2642\ufe0f",
[":man_gesturing_no::skin-tone-3:"] = "\U0001f645\U0001f3fd\u200d\u2642\ufe0f",
[":man_gesturing_no_tone4:"] = "\U0001f645\U0001f3fe\u200d\u2642\ufe0f",
[":man_gesturing_no_medium_dark_skin_tone:"] = "\U0001f645\U0001f3fe\u200d\u2642\ufe0f",
[":man_gesturing_no::skin-tone-4:"] = "\U0001f645\U0001f3fe\u200d\u2642\ufe0f",
[":man_gesturing_no_tone5:"] = "\U0001f645\U0001f3ff\u200d\u2642\ufe0f",
[":man_gesturing_no_dark_skin_tone:"] = "\U0001f645\U0001f3ff\u200d\u2642\ufe0f",
[":man_gesturing_no::skin-tone-5:"] = "\U0001f645\U0001f3ff\u200d\u2642\ufe0f",
[":man_gesturing_ok:"] = "\U0001f646\u200d\u2642\ufe0f",
[":man_gesturing_ok_tone1:"] = "\U0001f646\U0001f3fb\u200d\u2642\ufe0f",
[":man_gesturing_ok_light_skin_tone:"] = "\U0001f646\U0001f3fb\u200d\u2642\ufe0f",
[":man_gesturing_ok::skin-tone-1:"] = "\U0001f646\U0001f3fb\u200d\u2642\ufe0f",
[":man_gesturing_ok_tone2:"] = "\U0001f646\U0001f3fc\u200d\u2642\ufe0f",
[":man_gesturing_ok_medium_light_skin_tone:"] = "\U0001f646\U0001f3fc\u200d\u2642\ufe0f",
[":man_gesturing_ok::skin-tone-2:"] = "\U0001f646\U0001f3fc\u200d\u2642\ufe0f",
[":man_gesturing_ok_tone3:"] = "\U0001f646\U0001f3fd\u200d\u2642\ufe0f",
[":man_gesturing_ok_medium_skin_tone:"] = "\U0001f646\U0001f3fd\u200d\u2642\ufe0f",
[":man_gesturing_ok::skin-tone-3:"] = "\U0001f646\U0001f3fd\u200d\u2642\ufe0f",
[":man_gesturing_ok_tone4:"] = "\U0001f646\U0001f3fe\u200d\u2642\ufe0f",
[":man_gesturing_ok_medium_dark_skin_tone:"] = "\U0001f646\U0001f3fe\u200d\u2642\ufe0f",
[":man_gesturing_ok::skin-tone-4:"] = "\U0001f646\U0001f3fe\u200d\u2642\ufe0f",
[":man_gesturing_ok_tone5:"] = "\U0001f646\U0001f3ff\u200d\u2642\ufe0f",
[":man_gesturing_ok_dark_skin_tone:"] = "\U0001f646\U0001f3ff\u200d\u2642\ufe0f",
[":man_gesturing_ok::skin-tone-5:"] = "\U0001f646\U0001f3ff\u200d\u2642\ufe0f",
[":man_getting_face_massage:"] = "\U0001f486\u200d\u2642\ufe0f",
[":man_getting_face_massage_tone1:"] = "\U0001f486\U0001f3fb\u200d\u2642\ufe0f",
[":man_getting_face_massage_light_skin_tone:"] = "\U0001f486\U0001f3fb\u200d\u2642\ufe0f",
[":man_getting_face_massage::skin-tone-1:"] = "\U0001f486\U0001f3fb\u200d\u2642\ufe0f",
[":man_getting_face_massage_tone2:"] = "\U0001f486\U0001f3fc\u200d\u2642\ufe0f",
[":man_getting_face_massage_medium_light_skin_tone:"] = "\U0001f486\U0001f3fc\u200d\u2642\ufe0f",
[":man_getting_face_massage::skin-tone-2:"] = "\U0001f486\U0001f3fc\u200d\u2642\ufe0f",
[":man_getting_face_massage_tone3:"] = "\U0001f486\U0001f3fd\u200d\u2642\ufe0f",
[":man_getting_face_massage_medium_skin_tone:"] = "\U0001f486\U0001f3fd\u200d\u2642\ufe0f",
[":man_getting_face_massage::skin-tone-3:"] = "\U0001f486\U0001f3fd\u200d\u2642\ufe0f",
[":man_getting_face_massage_tone4:"] = "\U0001f486\U0001f3fe\u200d\u2642\ufe0f",
[":man_getting_face_massage_medium_dark_skin_tone:"] = "\U0001f486\U0001f3fe\u200d\u2642\ufe0f",
[":man_getting_face_massage::skin-tone-4:"] = "\U0001f486\U0001f3fe\u200d\u2642\ufe0f",
[":man_getting_face_massage_tone5:"] = "\U0001f486\U0001f3ff\u200d\u2642\ufe0f",
[":man_getting_face_massage_dark_skin_tone:"] = "\U0001f486\U0001f3ff\u200d\u2642\ufe0f",
[":man_getting_face_massage::skin-tone-5:"] = "\U0001f486\U0001f3ff\u200d\u2642\ufe0f",
[":man_getting_haircut:"] = "\U0001f487\u200d\u2642\ufe0f",
[":man_getting_haircut_tone1:"] = "\U0001f487\U0001f3fb\u200d\u2642\ufe0f",
[":man_getting_haircut_light_skin_tone:"] = "\U0001f487\U0001f3fb\u200d\u2642\ufe0f",
[":man_getting_haircut::skin-tone-1:"] = "\U0001f487\U0001f3fb\u200d\u2642\ufe0f",
[":man_getting_haircut_tone2:"] = "\U0001f487\U0001f3fc\u200d\u2642\ufe0f",
[":man_getting_haircut_medium_light_skin_tone:"] = "\U0001f487\U0001f3fc\u200d\u2642\ufe0f",
[":man_getting_haircut::skin-tone-2:"] = "\U0001f487\U0001f3fc\u200d\u2642\ufe0f",
[":man_getting_haircut_tone3:"] = "\U0001f487\U0001f3fd\u200d\u2642\ufe0f",
[":man_getting_haircut_medium_skin_tone:"] = "\U0001f487\U0001f3fd\u200d\u2642\ufe0f",
[":man_getting_haircut::skin-tone-3:"] = "\U0001f487\U0001f3fd\u200d\u2642\ufe0f",
[":man_getting_haircut_tone4:"] = "\U0001f487\U0001f3fe\u200d\u2642\ufe0f",
[":man_getting_haircut_medium_dark_skin_tone:"] = "\U0001f487\U0001f3fe\u200d\u2642\ufe0f",
[":man_getting_haircut::skin-tone-4:"] = "\U0001f487\U0001f3fe\u200d\u2642\ufe0f",
[":man_getting_haircut_tone5:"] = "\U0001f487\U0001f3ff\u200d\u2642\ufe0f",
[":man_getting_haircut_dark_skin_tone:"] = "\U0001f487\U0001f3ff\u200d\u2642\ufe0f",
[":man_getting_haircut::skin-tone-5:"] = "\U0001f487\U0001f3ff\u200d\u2642\ufe0f",
[":man_golfing:"] = "\U0001f3cc\ufe0f\u200d\u2642\ufe0f",
[":man_golfing_tone1:"] = "\U0001f3cc\U0001f3fb\u200d\u2642\ufe0f",
[":man_golfing_light_skin_tone:"] = "\U0001f3cc\U0001f3fb\u200d\u2642\ufe0f",
[":man_golfing::skin-tone-1:"] = "\U0001f3cc\U0001f3fb\u200d\u2642\ufe0f",
[":man_golfing_tone2:"] = "\U0001f3cc\U0001f3fc\u200d\u2642\ufe0f",
[":man_golfing_medium_light_skin_tone:"] = "\U0001f3cc\U0001f3fc\u200d\u2642\ufe0f",
[":man_golfing::skin-tone-2:"] = "\U0001f3cc\U0001f3fc\u200d\u2642\ufe0f",
[":man_golfing_tone3:"] = "\U0001f3cc\U0001f3fd\u200d\u2642\ufe0f",
[":man_golfing_medium_skin_tone:"] = "\U0001f3cc\U0001f3fd\u200d\u2642\ufe0f",
[":man_golfing::skin-tone-3:"] = "\U0001f3cc\U0001f3fd\u200d\u2642\ufe0f",
[":man_golfing_tone4:"] = "\U0001f3cc\U0001f3fe\u200d\u2642\ufe0f",
[":man_golfing_medium_dark_skin_tone:"] = "\U0001f3cc\U0001f3fe\u200d\u2642\ufe0f",
[":man_golfing::skin-tone-4:"] = "\U0001f3cc\U0001f3fe\u200d\u2642\ufe0f",
[":man_golfing_tone5:"] = "\U0001f3cc\U0001f3ff\u200d\u2642\ufe0f",
[":man_golfing_dark_skin_tone:"] = "\U0001f3cc\U0001f3ff\u200d\u2642\ufe0f",
[":man_golfing::skin-tone-5:"] = "\U0001f3cc\U0001f3ff\u200d\u2642\ufe0f",
[":man_guard:"] = "\U0001f482\u200d\u2642\ufe0f",
[":man_guard_tone1:"] = "\U0001f482\U0001f3fb\u200d\u2642\ufe0f",
[":man_guard_light_skin_tone:"] = "\U0001f482\U0001f3fb\u200d\u2642\ufe0f",
[":man_guard::skin-tone-1:"] = "\U0001f482\U0001f3fb\u200d\u2642\ufe0f",
[":man_guard_tone2:"] = "\U0001f482\U0001f3fc\u200d\u2642\ufe0f",
[":man_guard_medium_light_skin_tone:"] = "\U0001f482\U0001f3fc\u200d\u2642\ufe0f",
[":man_guard::skin-tone-2:"] = "\U0001f482\U0001f3fc\u200d\u2642\ufe0f",
[":man_guard_tone3:"] = "\U0001f482\U0001f3fd\u200d\u2642\ufe0f",
[":man_guard_medium_skin_tone:"] = "\U0001f482\U0001f3fd\u200d\u2642\ufe0f",
[":man_guard::skin-tone-3:"] = "\U0001f482\U0001f3fd\u200d\u2642\ufe0f",
[":man_guard_tone4:"] = "\U0001f482\U0001f3fe\u200d\u2642\ufe0f",
[":man_guard_medium_dark_skin_tone:"] = "\U0001f482\U0001f3fe\u200d\u2642\ufe0f",
[":man_guard::skin-tone-4:"] = "\U0001f482\U0001f3fe\u200d\u2642\ufe0f",
[":man_guard_tone5:"] = "\U0001f482\U0001f3ff\u200d\u2642\ufe0f",
[":man_guard_dark_skin_tone:"] = "\U0001f482\U0001f3ff\u200d\u2642\ufe0f",
[":man_guard::skin-tone-5:"] = "\U0001f482\U0001f3ff\u200d\u2642\ufe0f",
[":man_health_worker:"] = "\U0001f468\u200d\u2695\ufe0f",
[":man_health_worker_tone1:"] = "\U0001f468\U0001f3fb\u200d\u2695\ufe0f",
[":man_health_worker_light_skin_tone:"] = "\U0001f468\U0001f3fb\u200d\u2695\ufe0f",
[":man_health_worker::skin-tone-1:"] = "\U0001f468\U0001f3fb\u200d\u2695\ufe0f",
[":man_health_worker_tone2:"] = "\U0001f468\U0001f3fc\u200d\u2695\ufe0f",
[":man_health_worker_medium_light_skin_tone:"] = "\U0001f468\U0001f3fc\u200d\u2695\ufe0f",
[":man_health_worker::skin-tone-2:"] = "\U0001f468\U0001f3fc\u200d\u2695\ufe0f",
[":man_health_worker_tone3:"] = "\U0001f468\U0001f3fd\u200d\u2695\ufe0f",
[":man_health_worker_medium_skin_tone:"] = "\U0001f468\U0001f3fd\u200d\u2695\ufe0f",
[":man_health_worker::skin-tone-3:"] = "\U0001f468\U0001f3fd\u200d\u2695\ufe0f",
[":man_health_worker_tone4:"] = "\U0001f468\U0001f3fe\u200d\u2695\ufe0f",
[":man_health_worker_medium_dark_skin_tone:"] = "\U0001f468\U0001f3fe\u200d\u2695\ufe0f",
[":man_health_worker::skin-tone-4:"] = "\U0001f468\U0001f3fe\u200d\u2695\ufe0f",
[":man_health_worker_tone5:"] = "\U0001f468\U0001f3ff\u200d\u2695\ufe0f",
[":man_health_worker_dark_skin_tone:"] = "\U0001f468\U0001f3ff\u200d\u2695\ufe0f",
[":man_health_worker::skin-tone-5:"] = "\U0001f468\U0001f3ff\u200d\u2695\ufe0f",
[":man_in_lotus_position:"] = "\U0001f9d8\u200d\u2642\ufe0f",
[":man_in_lotus_position_tone1:"] = "\U0001f9d8\U0001f3fb\u200d\u2642\ufe0f",
[":man_in_lotus_position_light_skin_tone:"] = "\U0001f9d8\U0001f3fb\u200d\u2642\ufe0f",
[":man_in_lotus_position::skin-tone-1:"] = "\U0001f9d8\U0001f3fb\u200d\u2642\ufe0f",
[":man_in_lotus_position_tone2:"] = "\U0001f9d8\U0001f3fc\u200d\u2642\ufe0f",
[":man_in_lotus_position_medium_light_skin_tone:"] = "\U0001f9d8\U0001f3fc\u200d\u2642\ufe0f",
[":man_in_lotus_position::skin-tone-2:"] = "\U0001f9d8\U0001f3fc\u200d\u2642\ufe0f",
[":man_in_lotus_position_tone3:"] = "\U0001f9d8\U0001f3fd\u200d\u2642\ufe0f",
[":man_in_lotus_position_medium_skin_tone:"] = "\U0001f9d8\U0001f3fd\u200d\u2642\ufe0f",
[":man_in_lotus_position::skin-tone-3:"] = "\U0001f9d8\U0001f3fd\u200d\u2642\ufe0f",
[":man_in_lotus_position_tone4:"] = "\U0001f9d8\U0001f3fe\u200d\u2642\ufe0f",
[":man_in_lotus_position_medium_dark_skin_tone:"] = "\U0001f9d8\U0001f3fe\u200d\u2642\ufe0f",
[":man_in_lotus_position::skin-tone-4:"] = "\U0001f9d8\U0001f3fe\u200d\u2642\ufe0f",
[":man_in_lotus_position_tone5:"] = "\U0001f9d8\U0001f3ff\u200d\u2642\ufe0f",
[":man_in_lotus_position_dark_skin_tone:"] = "\U0001f9d8\U0001f3ff\u200d\u2642\ufe0f",
[":man_in_lotus_position::skin-tone-5:"] = "\U0001f9d8\U0001f3ff\u200d\u2642\ufe0f",
[":man_in_manual_wheelchair:"] = "\U0001f468\u200d\U0001f9bd",
[":man_in_manual_wheelchair_tone1:"] = "\U0001f468\U0001f3fb\u200d\U0001f9bd",
[":man_in_manual_wheelchair_light_skin_tone:"] = "\U0001f468\U0001f3fb\u200d\U0001f9bd",
[":man_in_manual_wheelchair::skin-tone-1:"] = "\U0001f468\U0001f3fb\u200d\U0001f9bd",
[":man_in_manual_wheelchair_tone2:"] = "\U0001f468\U0001f3fc\u200d\U0001f9bd",
[":man_in_manual_wheelchair_medium_light_skin_tone:"] = "\U0001f468\U0001f3fc\u200d\U0001f9bd",
[":man_in_manual_wheelchair::skin-tone-2:"] = "\U0001f468\U0001f3fc\u200d\U0001f9bd",
[":man_in_manual_wheelchair_tone3:"] = "\U0001f468\U0001f3fd\u200d\U0001f9bd",
[":man_in_manual_wheelchair_medium_skin_tone:"] = "\U0001f468\U0001f3fd\u200d\U0001f9bd",
[":man_in_manual_wheelchair::skin-tone-3:"] = "\U0001f468\U0001f3fd\u200d\U0001f9bd",
[":man_in_manual_wheelchair_tone4:"] = "\U0001f468\U0001f3fe\u200d\U0001f9bd",
[":man_in_manual_wheelchair_medium_dark_skin_tone:"] = "\U0001f468\U0001f3fe\u200d\U0001f9bd",
[":man_in_manual_wheelchair::skin-tone-4:"] = "\U0001f468\U0001f3fe\u200d\U0001f9bd",
[":man_in_manual_wheelchair_tone5:"] = "\U0001f468\U0001f3ff\u200d\U0001f9bd",
[":man_in_manual_wheelchair_dark_skin_tone:"] = "\U0001f468\U0001f3ff\u200d\U0001f9bd",
[":man_in_manual_wheelchair::skin-tone-5:"] = "\U0001f468\U0001f3ff\u200d\U0001f9bd",
[":man_in_motorized_wheelchair:"] = "\U0001f468\u200d\U0001f9bc",
[":man_in_motorized_wheelchair_tone1:"] = "\U0001f468\U0001f3fb\u200d\U0001f9bc",
[":man_in_motorized_wheelchair_light_skin_tone:"] = "\U0001f468\U0001f3fb\u200d\U0001f9bc",
[":man_in_motorized_wheelchair::skin-tone-1:"] = "\U0001f468\U0001f3fb\u200d\U0001f9bc",
[":man_in_motorized_wheelchair_tone2:"] = "\U0001f468\U0001f3fc\u200d\U0001f9bc",
[":man_in_motorized_wheelchair_medium_light_skin_tone:"] = "\U0001f468\U0001f3fc\u200d\U0001f9bc",
[":man_in_motorized_wheelchair::skin-tone-2:"] = "\U0001f468\U0001f3fc\u200d\U0001f9bc",
[":man_in_motorized_wheelchair_tone3:"] = "\U0001f468\U0001f3fd\u200d\U0001f9bc",
[":man_in_motorized_wheelchair_medium_skin_tone:"] = "\U0001f468\U0001f3fd\u200d\U0001f9bc",
[":man_in_motorized_wheelchair::skin-tone-3:"] = "\U0001f468\U0001f3fd\u200d\U0001f9bc",
[":man_in_motorized_wheelchair_tone4:"] = "\U0001f468\U0001f3fe\u200d\U0001f9bc",
[":man_in_motorized_wheelchair_medium_dark_skin_tone:"] = "\U0001f468\U0001f3fe\u200d\U0001f9bc",
[":man_in_motorized_wheelchair::skin-tone-4:"] = "\U0001f468\U0001f3fe\u200d\U0001f9bc",
[":man_in_motorized_wheelchair_tone5:"] = "\U0001f468\U0001f3ff\u200d\U0001f9bc",
[":man_in_motorized_wheelchair_dark_skin_tone:"] = "\U0001f468\U0001f3ff\u200d\U0001f9bc",
[":man_in_motorized_wheelchair::skin-tone-5:"] = "\U0001f468\U0001f3ff\u200d\U0001f9bc",
[":man_in_steamy_room:"] = "\U0001f9d6\u200d\u2642\ufe0f",
[":man_in_steamy_room_tone1:"] = "\U0001f9d6\U0001f3fb\u200d\u2642\ufe0f",
[":man_in_steamy_room_light_skin_tone:"] = "\U0001f9d6\U0001f3fb\u200d\u2642\ufe0f",
[":man_in_steamy_room::skin-tone-1:"] = "\U0001f9d6\U0001f3fb\u200d\u2642\ufe0f",
[":man_in_steamy_room_tone2:"] = "\U0001f9d6\U0001f3fc\u200d\u2642\ufe0f",
[":man_in_steamy_room_medium_light_skin_tone:"] = "\U0001f9d6\U0001f3fc\u200d\u2642\ufe0f",
[":man_in_steamy_room::skin-tone-2:"] = "\U0001f9d6\U0001f3fc\u200d\u2642\ufe0f",
[":man_in_steamy_room_tone3:"] = "\U0001f9d6\U0001f3fd\u200d\u2642\ufe0f",
[":man_in_steamy_room_medium_skin_tone:"] = "\U0001f9d6\U0001f3fd\u200d\u2642\ufe0f",
[":man_in_steamy_room::skin-tone-3:"] = "\U0001f9d6\U0001f3fd\u200d\u2642\ufe0f",
[":man_in_steamy_room_tone4:"] = "\U0001f9d6\U0001f3fe\u200d\u2642\ufe0f",
[":man_in_steamy_room_medium_dark_skin_tone:"] = "\U0001f9d6\U0001f3fe\u200d\u2642\ufe0f",
[":man_in_steamy_room::skin-tone-4:"] = "\U0001f9d6\U0001f3fe\u200d\u2642\ufe0f",
[":man_in_steamy_room_tone5:"] = "\U0001f9d6\U0001f3ff\u200d\u2642\ufe0f",
[":man_in_steamy_room_dark_skin_tone:"] = "\U0001f9d6\U0001f3ff\u200d\u2642\ufe0f",
[":man_in_steamy_room::skin-tone-5:"] = "\U0001f9d6\U0001f3ff\u200d\u2642\ufe0f",
[":man_in_tuxedo:"] = "\U0001f935\u200d\u2642\ufe0f",
[":man_in_tuxedo_tone1:"] = "\U0001f935\U0001f3fb\u200d\u2642\ufe0f",
[":man_in_tuxedo_light_skin_tone:"] = "\U0001f935\U0001f3fb\u200d\u2642\ufe0f",
[":man_in_tuxedo::skin-tone-1:"] = "\U0001f935\U0001f3fb\u200d\u2642\ufe0f",
[":man_in_tuxedo_tone2:"] = "\U0001f935\U0001f3fc\u200d\u2642\ufe0f",
[":man_in_tuxedo_medium_light_skin_tone:"] = "\U0001f935\U0001f3fc\u200d\u2642\ufe0f",
[":man_in_tuxedo::skin-tone-2:"] = "\U0001f935\U0001f3fc\u200d\u2642\ufe0f",
[":man_in_tuxedo_tone3:"] = "\U0001f935\U0001f3fd\u200d\u2642\ufe0f",
[":man_in_tuxedo_medium_skin_tone:"] = "\U0001f935\U0001f3fd\u200d\u2642\ufe0f",
[":man_in_tuxedo::skin-tone-3:"] = "\U0001f935\U0001f3fd\u200d\u2642\ufe0f",
[":man_in_tuxedo_tone4:"] = "\U0001f935\U0001f3fe\u200d\u2642\ufe0f",
[":man_in_tuxedo_medium_dark_skin_tone:"] = "\U0001f935\U0001f3fe\u200d\u2642\ufe0f",
[":man_in_tuxedo::skin-tone-4:"] = "\U0001f935\U0001f3fe\u200d\u2642\ufe0f",
[":man_in_tuxedo_tone5:"] = "\U0001f935\U0001f3ff\u200d\u2642\ufe0f",
[":man_in_tuxedo_dark_skin_tone:"] = "\U0001f935\U0001f3ff\u200d\u2642\ufe0f",
[":man_in_tuxedo::skin-tone-5:"] = "\U0001f935\U0001f3ff\u200d\u2642\ufe0f",
[":man_judge:"] = "\U0001f468\u200d\u2696\ufe0f",
[":man_judge_tone1:"] = "\U0001f468\U0001f3fb\u200d\u2696\ufe0f",
[":man_judge_light_skin_tone:"] = "\U0001f468\U0001f3fb\u200d\u2696\ufe0f",
[":man_judge::skin-tone-1:"] = "\U0001f468\U0001f3fb\u200d\u2696\ufe0f",
[":man_judge_tone2:"] = "\U0001f468\U0001f3fc\u200d\u2696\ufe0f",
[":man_judge_medium_light_skin_tone:"] = "\U0001f468\U0001f3fc\u200d\u2696\ufe0f",
[":man_judge::skin-tone-2:"] = "\U0001f468\U0001f3fc\u200d\u2696\ufe0f",
[":man_judge_tone3:"] = "\U0001f468\U0001f3fd\u200d\u2696\ufe0f",
[":man_judge_medium_skin_tone:"] = "\U0001f468\U0001f3fd\u200d\u2696\ufe0f",
[":man_judge::skin-tone-3:"] = "\U0001f468\U0001f3fd\u200d\u2696\ufe0f",
[":man_judge_tone4:"] = "\U0001f468\U0001f3fe\u200d\u2696\ufe0f",
[":man_judge_medium_dark_skin_tone:"] = "\U0001f468\U0001f3fe\u200d\u2696\ufe0f",
[":man_judge::skin-tone-4:"] = "\U0001f468\U0001f3fe\u200d\u2696\ufe0f",
[":man_judge_tone5:"] = "\U0001f468\U0001f3ff\u200d\u2696\ufe0f",
[":man_judge_dark_skin_tone:"] = "\U0001f468\U0001f3ff\u200d\u2696\ufe0f",
[":man_judge::skin-tone-5:"] = "\U0001f468\U0001f3ff\u200d\u2696\ufe0f",
[":man_juggling:"] = "\U0001f939\u200d\u2642\ufe0f",
[":man_juggling_tone1:"] = "\U0001f939\U0001f3fb\u200d\u2642\ufe0f",
[":man_juggling_light_skin_tone:"] = "\U0001f939\U0001f3fb\u200d\u2642\ufe0f",
[":man_juggling::skin-tone-1:"] = "\U0001f939\U0001f3fb\u200d\u2642\ufe0f",
[":man_juggling_tone2:"] = "\U0001f939\U0001f3fc\u200d\u2642\ufe0f",
[":man_juggling_medium_light_skin_tone:"] = "\U0001f939\U0001f3fc\u200d\u2642\ufe0f",
[":man_juggling::skin-tone-2:"] = "\U0001f939\U0001f3fc\u200d\u2642\ufe0f",
[":man_juggling_tone3:"] = "\U0001f939\U0001f3fd\u200d\u2642\ufe0f",
[":man_juggling_medium_skin_tone:"] = "\U0001f939\U0001f3fd\u200d\u2642\ufe0f",
[":man_juggling::skin-tone-3:"] = "\U0001f939\U0001f3fd\u200d\u2642\ufe0f",
[":man_juggling_tone4:"] = "\U0001f939\U0001f3fe\u200d\u2642\ufe0f",
[":man_juggling_medium_dark_skin_tone:"] = "\U0001f939\U0001f3fe\u200d\u2642\ufe0f",
[":man_juggling::skin-tone-4:"] = "\U0001f939\U0001f3fe\u200d\u2642\ufe0f",
[":man_juggling_tone5:"] = "\U0001f939\U0001f3ff\u200d\u2642\ufe0f",
[":man_juggling_dark_skin_tone:"] = "\U0001f939\U0001f3ff\u200d\u2642\ufe0f",
[":man_juggling::skin-tone-5:"] = "\U0001f939\U0001f3ff\u200d\u2642\ufe0f",
[":man_kneeling:"] = "\U0001f9ce\u200d\u2642\ufe0f",
[":man_kneeling_tone1:"] = "\U0001f9ce\U0001f3fb\u200d\u2642\ufe0f",
[":man_kneeling_light_skin_tone:"] = "\U0001f9ce\U0001f3fb\u200d\u2642\ufe0f",
[":man_kneeling::skin-tone-1:"] = "\U0001f9ce\U0001f3fb\u200d\u2642\ufe0f",
[":man_kneeling_tone2:"] = "\U0001f9ce\U0001f3fc\u200d\u2642\ufe0f",
[":man_kneeling_medium_light_skin_tone:"] = "\U0001f9ce\U0001f3fc\u200d\u2642\ufe0f",
[":man_kneeling::skin-tone-2:"] = "\U0001f9ce\U0001f3fc\u200d\u2642\ufe0f",
[":man_kneeling_tone3:"] = "\U0001f9ce\U0001f3fd\u200d\u2642\ufe0f",
[":man_kneeling_medium_skin_tone:"] = "\U0001f9ce\U0001f3fd\u200d\u2642\ufe0f",
[":man_kneeling::skin-tone-3:"] = "\U0001f9ce\U0001f3fd\u200d\u2642\ufe0f",
[":man_kneeling_tone4:"] = "\U0001f9ce\U0001f3fe\u200d\u2642\ufe0f",
[":man_kneeling_medium_dark_skin_tone:"] = "\U0001f9ce\U0001f3fe\u200d\u2642\ufe0f",
[":man_kneeling::skin-tone-4:"] = "\U0001f9ce\U0001f3fe\u200d\u2642\ufe0f",
[":man_kneeling_tone5:"] = "\U0001f9ce\U0001f3ff\u200d\u2642\ufe0f",
[":man_kneeling_dark_skin_tone:"] = "\U0001f9ce\U0001f3ff\u200d\u2642\ufe0f",
[":man_kneeling::skin-tone-5:"] = "\U0001f9ce\U0001f3ff\u200d\u2642\ufe0f",
[":man_lifting_weights:"] = "\U0001f3cb\ufe0f\u200d\u2642\ufe0f",
[":man_lifting_weights_tone1:"] = "\U0001f3cb\U0001f3fb\u200d\u2642\ufe0f",
[":man_lifting_weights_light_skin_tone:"] = "\U0001f3cb\U0001f3fb\u200d\u2642\ufe0f",
[":man_lifting_weights::skin-tone-1:"] = "\U0001f3cb\U0001f3fb\u200d\u2642\ufe0f",
[":man_lifting_weights_tone2:"] = "\U0001f3cb\U0001f3fc\u200d\u2642\ufe0f",
[":man_lifting_weights_medium_light_skin_tone:"] = "\U0001f3cb\U0001f3fc\u200d\u2642\ufe0f",
[":man_lifting_weights::skin-tone-2:"] = "\U0001f3cb\U0001f3fc\u200d\u2642\ufe0f",
[":man_lifting_weights_tone3:"] = "\U0001f3cb\U0001f3fd\u200d\u2642\ufe0f",
[":man_lifting_weights_medium_skin_tone:"] = "\U0001f3cb\U0001f3fd\u200d\u2642\ufe0f",
[":man_lifting_weights::skin-tone-3:"] = "\U0001f3cb\U0001f3fd\u200d\u2642\ufe0f",
[":man_lifting_weights_tone4:"] = "\U0001f3cb\U0001f3fe\u200d\u2642\ufe0f",
[":man_lifting_weights_medium_dark_skin_tone:"] = "\U0001f3cb\U0001f3fe\u200d\u2642\ufe0f",
[":man_lifting_weights::skin-tone-4:"] = "\U0001f3cb\U0001f3fe\u200d\u2642\ufe0f",
[":man_lifting_weights_tone5:"] = "\U0001f3cb\U0001f3ff\u200d\u2642\ufe0f",
[":man_lifting_weights_dark_skin_tone:"] = "\U0001f3cb\U0001f3ff\u200d\u2642\ufe0f",
[":man_lifting_weights::skin-tone-5:"] = "\U0001f3cb\U0001f3ff\u200d\u2642\ufe0f",
[":man_mage:"] = "\U0001f9d9\u200d\u2642\ufe0f",
[":man_mage_tone1:"] = "\U0001f9d9\U0001f3fb\u200d\u2642\ufe0f",
[":man_mage_light_skin_tone:"] = "\U0001f9d9\U0001f3fb\u200d\u2642\ufe0f",
[":man_mage::skin-tone-1:"] = "\U0001f9d9\U0001f3fb\u200d\u2642\ufe0f",
[":man_mage_tone2:"] = "\U0001f9d9\U0001f3fc\u200d\u2642\ufe0f",
[":man_mage_medium_light_skin_tone:"] = "\U0001f9d9\U0001f3fc\u200d\u2642\ufe0f",
[":man_mage::skin-tone-2:"] = "\U0001f9d9\U0001f3fc\u200d\u2642\ufe0f",
[":man_mage_tone3:"] = "\U0001f9d9\U0001f3fd\u200d\u2642\ufe0f",
[":man_mage_medium_skin_tone:"] = "\U0001f9d9\U0001f3fd\u200d\u2642\ufe0f",
[":man_mage::skin-tone-3:"] = "\U0001f9d9\U0001f3fd\u200d\u2642\ufe0f",
[":man_mage_tone4:"] = "\U0001f9d9\U0001f3fe\u200d\u2642\ufe0f",
[":man_mage_medium_dark_skin_tone:"] = "\U0001f9d9\U0001f3fe\u200d\u2642\ufe0f",
[":man_mage::skin-tone-4:"] = "\U0001f9d9\U0001f3fe\u200d\u2642\ufe0f",
[":man_mage_tone5:"] = "\U0001f9d9\U0001f3ff\u200d\u2642\ufe0f",
[":man_mage_dark_skin_tone:"] = "\U0001f9d9\U0001f3ff\u200d\u2642\ufe0f",
[":man_mage::skin-tone-5:"] = "\U0001f9d9\U0001f3ff\u200d\u2642\ufe0f",
[":man_mechanic:"] = "\U0001f468\u200d\U0001f527",
[":man_mechanic_tone1:"] = "\U0001f468\U0001f3fb\u200d\U0001f527",
[":man_mechanic_light_skin_tone:"] = "\U0001f468\U0001f3fb\u200d\U0001f527",
[":man_mechanic::skin-tone-1:"] = "\U0001f468\U0001f3fb\u200d\U0001f527",
[":man_mechanic_tone2:"] = "\U0001f468\U0001f3fc\u200d\U0001f527",
[":man_mechanic_medium_light_skin_tone:"] = "\U0001f468\U0001f3fc\u200d\U0001f527",
[":man_mechanic::skin-tone-2:"] = "\U0001f468\U0001f3fc\u200d\U0001f527",
[":man_mechanic_tone3:"] = "\U0001f468\U0001f3fd\u200d\U0001f527",
[":man_mechanic_medium_skin_tone:"] = "\U0001f468\U0001f3fd\u200d\U0001f527",
[":man_mechanic::skin-tone-3:"] = "\U0001f468\U0001f3fd\u200d\U0001f527",
[":man_mechanic_tone4:"] = "\U0001f468\U0001f3fe\u200d\U0001f527",
[":man_mechanic_medium_dark_skin_tone:"] = "\U0001f468\U0001f3fe\u200d\U0001f527",
[":man_mechanic::skin-tone-4:"] = "\U0001f468\U0001f3fe\u200d\U0001f527",
[":man_mechanic_tone5:"] = "\U0001f468\U0001f3ff\u200d\U0001f527",
[":man_mechanic_dark_skin_tone:"] = "\U0001f468\U0001f3ff\u200d\U0001f527",
[":man_mechanic::skin-tone-5:"] = "\U0001f468\U0001f3ff\u200d\U0001f527",
[":man_mountain_biking:"] = "\U0001f6b5\u200d\u2642\ufe0f",
[":man_mountain_biking_tone1:"] = "\U0001f6b5\U0001f3fb\u200d\u2642\ufe0f",
[":man_mountain_biking_light_skin_tone:"] = "\U0001f6b5\U0001f3fb\u200d\u2642\ufe0f",
[":man_mountain_biking::skin-tone-1:"] = "\U0001f6b5\U0001f3fb\u200d\u2642\ufe0f",
[":man_mountain_biking_tone2:"] = "\U0001f6b5\U0001f3fc\u200d\u2642\ufe0f",
[":man_mountain_biking_medium_light_skin_tone:"] = "\U0001f6b5\U0001f3fc\u200d\u2642\ufe0f",
[":man_mountain_biking::skin-tone-2:"] = "\U0001f6b5\U0001f3fc\u200d\u2642\ufe0f",
[":man_mountain_biking_tone3:"] = "\U0001f6b5\U0001f3fd\u200d\u2642\ufe0f",
[":man_mountain_biking_medium_skin_tone:"] = "\U0001f6b5\U0001f3fd\u200d\u2642\ufe0f",
[":man_mountain_biking::skin-tone-3:"] = "\U0001f6b5\U0001f3fd\u200d\u2642\ufe0f",
[":man_mountain_biking_tone4:"] = "\U0001f6b5\U0001f3fe\u200d\u2642\ufe0f",
[":man_mountain_biking_medium_dark_skin_tone:"] = "\U0001f6b5\U0001f3fe\u200d\u2642\ufe0f",
[":man_mountain_biking::skin-tone-4:"] = "\U0001f6b5\U0001f3fe\u200d\u2642\ufe0f",
[":man_mountain_biking_tone5:"] = "\U0001f6b5\U0001f3ff\u200d\u2642\ufe0f",
[":man_mountain_biking_dark_skin_tone:"] = "\U0001f6b5\U0001f3ff\u200d\u2642\ufe0f",
[":man_mountain_biking::skin-tone-5:"] = "\U0001f6b5\U0001f3ff\u200d\u2642\ufe0f",
[":man_office_worker:"] = "\U0001f468\u200d\U0001f4bc",
[":man_office_worker_tone1:"] = "\U0001f468\U0001f3fb\u200d\U0001f4bc",
[":man_office_worker_light_skin_tone:"] = "\U0001f468\U0001f3fb\u200d\U0001f4bc",
[":man_office_worker::skin-tone-1:"] = "\U0001f468\U0001f3fb\u200d\U0001f4bc",
[":man_office_worker_tone2:"] = "\U0001f468\U0001f3fc\u200d\U0001f4bc",
[":man_office_worker_medium_light_skin_tone:"] = "\U0001f468\U0001f3fc\u200d\U0001f4bc",
[":man_office_worker::skin-tone-2:"] = "\U0001f468\U0001f3fc\u200d\U0001f4bc",
[":man_office_worker_tone3:"] = "\U0001f468\U0001f3fd\u200d\U0001f4bc",
[":man_office_worker_medium_skin_tone:"] = "\U0001f468\U0001f3fd\u200d\U0001f4bc",
[":man_office_worker::skin-tone-3:"] = "\U0001f468\U0001f3fd\u200d\U0001f4bc",
[":man_office_worker_tone4:"] = "\U0001f468\U0001f3fe\u200d\U0001f4bc",
[":man_office_worker_medium_dark_skin_tone:"] = "\U0001f468\U0001f3fe\u200d\U0001f4bc",
[":man_office_worker::skin-tone-4:"] = "\U0001f468\U0001f3fe\u200d\U0001f4bc",
[":man_office_worker_tone5:"] = "\U0001f468\U0001f3ff\u200d\U0001f4bc",
[":man_office_worker_dark_skin_tone:"] = "\U0001f468\U0001f3ff\u200d\U0001f4bc",
[":man_office_worker::skin-tone-5:"] = "\U0001f468\U0001f3ff\u200d\U0001f4bc",
[":man_pilot:"] = "\U0001f468\u200d\u2708\ufe0f",
[":man_pilot_tone1:"] = "\U0001f468\U0001f3fb\u200d\u2708\ufe0f",
[":man_pilot_light_skin_tone:"] = "\U0001f468\U0001f3fb\u200d\u2708\ufe0f",
[":man_pilot::skin-tone-1:"] = "\U0001f468\U0001f3fb\u200d\u2708\ufe0f",
[":man_pilot_tone2:"] = "\U0001f468\U0001f3fc\u200d\u2708\ufe0f",
[":man_pilot_medium_light_skin_tone:"] = "\U0001f468\U0001f3fc\u200d\u2708\ufe0f",
[":man_pilot::skin-tone-2:"] = "\U0001f468\U0001f3fc\u200d\u2708\ufe0f",
[":man_pilot_tone3:"] = "\U0001f468\U0001f3fd\u200d\u2708\ufe0f",
[":man_pilot_medium_skin_tone:"] = "\U0001f468\U0001f3fd\u200d\u2708\ufe0f",
[":man_pilot::skin-tone-3:"] = "\U0001f468\U0001f3fd\u200d\u2708\ufe0f",
[":man_pilot_tone4:"] = "\U0001f468\U0001f3fe\u200d\u2708\ufe0f",
[":man_pilot_medium_dark_skin_tone:"] = "\U0001f468\U0001f3fe\u200d\u2708\ufe0f",
[":man_pilot::skin-tone-4:"] = "\U0001f468\U0001f3fe\u200d\u2708\ufe0f",
[":man_pilot_tone5:"] = "\U0001f468\U0001f3ff\u200d\u2708\ufe0f",
[":man_pilot_dark_skin_tone:"] = "\U0001f468\U0001f3ff\u200d\u2708\ufe0f",
[":man_pilot::skin-tone-5:"] = "\U0001f468\U0001f3ff\u200d\u2708\ufe0f",
[":man_playing_handball:"] = "\U0001f93e\u200d\u2642\ufe0f",
[":man_playing_handball_tone1:"] = "\U0001f93e\U0001f3fb\u200d\u2642\ufe0f",
[":man_playing_handball_light_skin_tone:"] = "\U0001f93e\U0001f3fb\u200d\u2642\ufe0f",
[":man_playing_handball::skin-tone-1:"] = "\U0001f93e\U0001f3fb\u200d\u2642\ufe0f",
[":man_playing_handball_tone2:"] = "\U0001f93e\U0001f3fc\u200d\u2642\ufe0f",
[":man_playing_handball_medium_light_skin_tone:"] = "\U0001f93e\U0001f3fc\u200d\u2642\ufe0f",
[":man_playing_handball::skin-tone-2:"] = "\U0001f93e\U0001f3fc\u200d\u2642\ufe0f",
[":man_playing_handball_tone3:"] = "\U0001f93e\U0001f3fd\u200d\u2642\ufe0f",
[":man_playing_handball_medium_skin_tone:"] = "\U0001f93e\U0001f3fd\u200d\u2642\ufe0f",
[":man_playing_handball::skin-tone-3:"] = "\U0001f93e\U0001f3fd\u200d\u2642\ufe0f",
[":man_playing_handball_tone4:"] = "\U0001f93e\U0001f3fe\u200d\u2642\ufe0f",
[":man_playing_handball_medium_dark_skin_tone:"] = "\U0001f93e\U0001f3fe\u200d\u2642\ufe0f",
[":man_playing_handball::skin-tone-4:"] = "\U0001f93e\U0001f3fe\u200d\u2642\ufe0f",
[":man_playing_handball_tone5:"] = "\U0001f93e\U0001f3ff\u200d\u2642\ufe0f",
[":man_playing_handball_dark_skin_tone:"] = "\U0001f93e\U0001f3ff\u200d\u2642\ufe0f",
[":man_playing_handball::skin-tone-5:"] = "\U0001f93e\U0001f3ff\u200d\u2642\ufe0f",
[":man_playing_water_polo:"] = "\U0001f93d\u200d\u2642\ufe0f",
[":man_playing_water_polo_tone1:"] = "\U0001f93d\U0001f3fb\u200d\u2642\ufe0f",
[":man_playing_water_polo_light_skin_tone:"] = "\U0001f93d\U0001f3fb\u200d\u2642\ufe0f",
[":man_playing_water_polo::skin-tone-1:"] = "\U0001f93d\U0001f3fb\u200d\u2642\ufe0f",
[":man_playing_water_polo_tone2:"] = "\U0001f93d\U0001f3fc\u200d\u2642\ufe0f",
[":man_playing_water_polo_medium_light_skin_tone:"] = "\U0001f93d\U0001f3fc\u200d\u2642\ufe0f",
[":man_playing_water_polo::skin-tone-2:"] = "\U0001f93d\U0001f3fc\u200d\u2642\ufe0f",
[":man_playing_water_polo_tone3:"] = "\U0001f93d\U0001f3fd\u200d\u2642\ufe0f",
[":man_playing_water_polo_medium_skin_tone:"] = "\U0001f93d\U0001f3fd\u200d\u2642\ufe0f",
[":man_playing_water_polo::skin-tone-3:"] = "\U0001f93d\U0001f3fd\u200d\u2642\ufe0f",
[":man_playing_water_polo_tone4:"] = "\U0001f93d\U0001f3fe\u200d\u2642\ufe0f",
[":man_playing_water_polo_medium_dark_skin_tone:"] = "\U0001f93d\U0001f3fe\u200d\u2642\ufe0f",
[":man_playing_water_polo::skin-tone-4:"] = "\U0001f93d\U0001f3fe\u200d\u2642\ufe0f",
[":man_playing_water_polo_tone5:"] = "\U0001f93d\U0001f3ff\u200d\u2642\ufe0f",
[":man_playing_water_polo_dark_skin_tone:"] = "\U0001f93d\U0001f3ff\u200d\u2642\ufe0f",
[":man_playing_water_polo::skin-tone-5:"] = "\U0001f93d\U0001f3ff\u200d\u2642\ufe0f",
[":man_police_officer:"] = "\U0001f46e\u200d\u2642\ufe0f",
[":man_police_officer_tone1:"] = "\U0001f46e\U0001f3fb\u200d\u2642\ufe0f",
[":man_police_officer_light_skin_tone:"] = "\U0001f46e\U0001f3fb\u200d\u2642\ufe0f",
[":man_police_officer::skin-tone-1:"] = "\U0001f46e\U0001f3fb\u200d\u2642\ufe0f",
[":man_police_officer_tone2:"] = "\U0001f46e\U0001f3fc\u200d\u2642\ufe0f",
[":man_police_officer_medium_light_skin_tone:"] = "\U0001f46e\U0001f3fc\u200d\u2642\ufe0f",
[":man_police_officer::skin-tone-2:"] = "\U0001f46e\U0001f3fc\u200d\u2642\ufe0f",
[":man_police_officer_tone3:"] = "\U0001f46e\U0001f3fd\u200d\u2642\ufe0f",
[":man_police_officer_medium_skin_tone:"] = "\U0001f46e\U0001f3fd\u200d\u2642\ufe0f",
[":man_police_officer::skin-tone-3:"] = "\U0001f46e\U0001f3fd\u200d\u2642\ufe0f",
[":man_police_officer_tone4:"] = "\U0001f46e\U0001f3fe\u200d\u2642\ufe0f",
[":man_police_officer_medium_dark_skin_tone:"] = "\U0001f46e\U0001f3fe\u200d\u2642\ufe0f",
[":man_police_officer::skin-tone-4:"] = "\U0001f46e\U0001f3fe\u200d\u2642\ufe0f",
[":man_police_officer_tone5:"] = "\U0001f46e\U0001f3ff\u200d\u2642\ufe0f",
[":man_police_officer_dark_skin_tone:"] = "\U0001f46e\U0001f3ff\u200d\u2642\ufe0f",
[":man_police_officer::skin-tone-5:"] = "\U0001f46e\U0001f3ff\u200d\u2642\ufe0f",
[":man_pouting:"] = "\U0001f64e\u200d\u2642\ufe0f",
[":man_pouting_tone1:"] = "\U0001f64e\U0001f3fb\u200d\u2642\ufe0f",
[":man_pouting_light_skin_tone:"] = "\U0001f64e\U0001f3fb\u200d\u2642\ufe0f",
[":man_pouting::skin-tone-1:"] = "\U0001f64e\U0001f3fb\u200d\u2642\ufe0f",
[":man_pouting_tone2:"] = "\U0001f64e\U0001f3fc\u200d\u2642\ufe0f",
[":man_pouting_medium_light_skin_tone:"] = "\U0001f64e\U0001f3fc\u200d\u2642\ufe0f",
[":man_pouting::skin-tone-2:"] = "\U0001f64e\U0001f3fc\u200d\u2642\ufe0f",
[":man_pouting_tone3:"] = "\U0001f64e\U0001f3fd\u200d\u2642\ufe0f",
[":man_pouting_medium_skin_tone:"] = "\U0001f64e\U0001f3fd\u200d\u2642\ufe0f",
[":man_pouting::skin-tone-3:"] = "\U0001f64e\U0001f3fd\u200d\u2642\ufe0f",
[":man_pouting_tone4:"] = "\U0001f64e\U0001f3fe\u200d\u2642\ufe0f",
[":man_pouting_medium_dark_skin_tone:"] = "\U0001f64e\U0001f3fe\u200d\u2642\ufe0f",
[":man_pouting::skin-tone-4:"] = "\U0001f64e\U0001f3fe\u200d\u2642\ufe0f",
[":man_pouting_tone5:"] = "\U0001f64e\U0001f3ff\u200d\u2642\ufe0f",
[":man_pouting_dark_skin_tone:"] = "\U0001f64e\U0001f3ff\u200d\u2642\ufe0f",
[":man_pouting::skin-tone-5:"] = "\U0001f64e\U0001f3ff\u200d\u2642\ufe0f",
[":man_raising_hand:"] = "\U0001f64b\u200d\u2642\ufe0f",
[":man_raising_hand_tone1:"] = "\U0001f64b\U0001f3fb\u200d\u2642\ufe0f",
[":man_raising_hand_light_skin_tone:"] = "\U0001f64b\U0001f3fb\u200d\u2642\ufe0f",
[":man_raising_hand::skin-tone-1:"] = "\U0001f64b\U0001f3fb\u200d\u2642\ufe0f",
[":man_raising_hand_tone2:"] = "\U0001f64b\U0001f3fc\u200d\u2642\ufe0f",
[":man_raising_hand_medium_light_skin_tone:"] = "\U0001f64b\U0001f3fc\u200d\u2642\ufe0f",
[":man_raising_hand::skin-tone-2:"] = "\U0001f64b\U0001f3fc\u200d\u2642\ufe0f",
[":man_raising_hand_tone3:"] = "\U0001f64b\U0001f3fd\u200d\u2642\ufe0f",
[":man_raising_hand_medium_skin_tone:"] = "\U0001f64b\U0001f3fd\u200d\u2642\ufe0f",
[":man_raising_hand::skin-tone-3:"] = "\U0001f64b\U0001f3fd\u200d\u2642\ufe0f",
[":man_raising_hand_tone4:"] = "\U0001f64b\U0001f3fe\u200d\u2642\ufe0f",
[":man_raising_hand_medium_dark_skin_tone:"] = "\U0001f64b\U0001f3fe\u200d\u2642\ufe0f",
[":man_raising_hand::skin-tone-4:"] = "\U0001f64b\U0001f3fe\u200d\u2642\ufe0f",
[":man_raising_hand_tone5:"] = "\U0001f64b\U0001f3ff\u200d\u2642\ufe0f",
[":man_raising_hand_dark_skin_tone:"] = "\U0001f64b\U0001f3ff\u200d\u2642\ufe0f",
[":man_raising_hand::skin-tone-5:"] = "\U0001f64b\U0001f3ff\u200d\u2642\ufe0f",
[":man_red_haired:"] = "\U0001f468\u200d\U0001f9b0",
[":man_red_haired_tone1:"] = "\U0001f468\U0001f3fb\u200d\U0001f9b0",
[":man_red_haired_light_skin_tone:"] = "\U0001f468\U0001f3fb\u200d\U0001f9b0",
[":man_red_haired::skin-tone-1:"] = "\U0001f468\U0001f3fb\u200d\U0001f9b0",
[":man_red_haired_tone2:"] = "\U0001f468\U0001f3fc\u200d\U0001f9b0",
[":man_red_haired_medium_light_skin_tone:"] = "\U0001f468\U0001f3fc\u200d\U0001f9b0",
[":man_red_haired::skin-tone-2:"] = "\U0001f468\U0001f3fc\u200d\U0001f9b0",
[":man_red_haired_tone3:"] = "\U0001f468\U0001f3fd\u200d\U0001f9b0",
[":man_red_haired_medium_skin_tone:"] = "\U0001f468\U0001f3fd\u200d\U0001f9b0",
[":man_red_haired::skin-tone-3:"] = "\U0001f468\U0001f3fd\u200d\U0001f9b0",
[":man_red_haired_tone4:"] = "\U0001f468\U0001f3fe\u200d\U0001f9b0",
[":man_red_haired_medium_dark_skin_tone:"] = "\U0001f468\U0001f3fe\u200d\U0001f9b0",
[":man_red_haired::skin-tone-4:"] = "\U0001f468\U0001f3fe\u200d\U0001f9b0",
[":man_red_haired_tone5:"] = "\U0001f468\U0001f3ff\u200d\U0001f9b0",
[":man_red_haired_dark_skin_tone:"] = "\U0001f468\U0001f3ff\u200d\U0001f9b0",
[":man_red_haired::skin-tone-5:"] = "\U0001f468\U0001f3ff\u200d\U0001f9b0",
[":man_rowing_boat:"] = "\U0001f6a3\u200d\u2642\ufe0f",
[":man_rowing_boat_tone1:"] = "\U0001f6a3\U0001f3fb\u200d\u2642\ufe0f",
[":man_rowing_boat_light_skin_tone:"] = "\U0001f6a3\U0001f3fb\u200d\u2642\ufe0f",
[":man_rowing_boat::skin-tone-1:"] = "\U0001f6a3\U0001f3fb\u200d\u2642\ufe0f",
[":man_rowing_boat_tone2:"] = "\U0001f6a3\U0001f3fc\u200d\u2642\ufe0f",
[":man_rowing_boat_medium_light_skin_tone:"] = "\U0001f6a3\U0001f3fc\u200d\u2642\ufe0f",
[":man_rowing_boat::skin-tone-2:"] = "\U0001f6a3\U0001f3fc\u200d\u2642\ufe0f",
[":man_rowing_boat_tone3:"] = "\U0001f6a3\U0001f3fd\u200d\u2642\ufe0f",
[":man_rowing_boat_medium_skin_tone:"] = "\U0001f6a3\U0001f3fd\u200d\u2642\ufe0f",
[":man_rowing_boat::skin-tone-3:"] = "\U0001f6a3\U0001f3fd\u200d\u2642\ufe0f",
[":man_rowing_boat_tone4:"] = "\U0001f6a3\U0001f3fe\u200d\u2642\ufe0f",
[":man_rowing_boat_medium_dark_skin_tone:"] = "\U0001f6a3\U0001f3fe\u200d\u2642\ufe0f",
[":man_rowing_boat::skin-tone-4:"] = "\U0001f6a3\U0001f3fe\u200d\u2642\ufe0f",
[":man_rowing_boat_tone5:"] = "\U0001f6a3\U0001f3ff\u200d\u2642\ufe0f",
[":man_rowing_boat_dark_skin_tone:"] = "\U0001f6a3\U0001f3ff\u200d\u2642\ufe0f",
[":man_rowing_boat::skin-tone-5:"] = "\U0001f6a3\U0001f3ff\u200d\u2642\ufe0f",
[":man_running:"] = "\U0001f3c3\u200d\u2642\ufe0f",
[":man_running_tone1:"] = "\U0001f3c3\U0001f3fb\u200d\u2642\ufe0f",
[":man_running_light_skin_tone:"] = "\U0001f3c3\U0001f3fb\u200d\u2642\ufe0f",
[":man_running::skin-tone-1:"] = "\U0001f3c3\U0001f3fb\u200d\u2642\ufe0f",
[":man_running_tone2:"] = "\U0001f3c3\U0001f3fc\u200d\u2642\ufe0f",
[":man_running_medium_light_skin_tone:"] = "\U0001f3c3\U0001f3fc\u200d\u2642\ufe0f",
[":man_running::skin-tone-2:"] = "\U0001f3c3\U0001f3fc\u200d\u2642\ufe0f",
[":man_running_tone3:"] = "\U0001f3c3\U0001f3fd\u200d\u2642\ufe0f",
[":man_running_medium_skin_tone:"] = "\U0001f3c3\U0001f3fd\u200d\u2642\ufe0f",
[":man_running::skin-tone-3:"] = "\U0001f3c3\U0001f3fd\u200d\u2642\ufe0f",
[":man_running_tone4:"] = "\U0001f3c3\U0001f3fe\u200d\u2642\ufe0f",
[":man_running_medium_dark_skin_tone:"] = "\U0001f3c3\U0001f3fe\u200d\u2642\ufe0f",
[":man_running::skin-tone-4:"] = "\U0001f3c3\U0001f3fe\u200d\u2642\ufe0f",
[":man_running_tone5:"] = "\U0001f3c3\U0001f3ff\u200d\u2642\ufe0f",
[":man_running_dark_skin_tone:"] = "\U0001f3c3\U0001f3ff\u200d\u2642\ufe0f",
[":man_running::skin-tone-5:"] = "\U0001f3c3\U0001f3ff\u200d\u2642\ufe0f",
[":man_scientist:"] = "\U0001f468\u200d\U0001f52c",
[":man_scientist_tone1:"] = "\U0001f468\U0001f3fb\u200d\U0001f52c",
[":man_scientist_light_skin_tone:"] = "\U0001f468\U0001f3fb\u200d\U0001f52c",
[":man_scientist::skin-tone-1:"] = "\U0001f468\U0001f3fb\u200d\U0001f52c",
[":man_scientist_tone2:"] = "\U0001f468\U0001f3fc\u200d\U0001f52c",
[":man_scientist_medium_light_skin_tone:"] = "\U0001f468\U0001f3fc\u200d\U0001f52c",
[":man_scientist::skin-tone-2:"] = "\U0001f468\U0001f3fc\u200d\U0001f52c",
[":man_scientist_tone3:"] = "\U0001f468\U0001f3fd\u200d\U0001f52c",
[":man_scientist_medium_skin_tone:"] = "\U0001f468\U0001f3fd\u200d\U0001f52c",
[":man_scientist::skin-tone-3:"] = "\U0001f468\U0001f3fd\u200d\U0001f52c",
[":man_scientist_tone4:"] = "\U0001f468\U0001f3fe\u200d\U0001f52c",
[":man_scientist_medium_dark_skin_tone:"] = "\U0001f468\U0001f3fe\u200d\U0001f52c",
[":man_scientist::skin-tone-4:"] = "\U0001f468\U0001f3fe\u200d\U0001f52c",
[":man_scientist_tone5:"] = "\U0001f468\U0001f3ff\u200d\U0001f52c",
[":man_scientist_dark_skin_tone:"] = "\U0001f468\U0001f3ff\u200d\U0001f52c",
[":man_scientist::skin-tone-5:"] = "\U0001f468\U0001f3ff\u200d\U0001f52c",
[":man_shrugging:"] = "\U0001f937\u200d\u2642\ufe0f",
[":man_shrugging_tone1:"] = "\U0001f937\U0001f3fb\u200d\u2642\ufe0f",
[":man_shrugging_light_skin_tone:"] = "\U0001f937\U0001f3fb\u200d\u2642\ufe0f",
[":man_shrugging::skin-tone-1:"] = "\U0001f937\U0001f3fb\u200d\u2642\ufe0f",
[":man_shrugging_tone2:"] = "\U0001f937\U0001f3fc\u200d\u2642\ufe0f",
[":man_shrugging_medium_light_skin_tone:"] = "\U0001f937\U0001f3fc\u200d\u2642\ufe0f",
[":man_shrugging::skin-tone-2:"] = "\U0001f937\U0001f3fc\u200d\u2642\ufe0f",
[":man_shrugging_tone3:"] = "\U0001f937\U0001f3fd\u200d\u2642\ufe0f",
[":man_shrugging_medium_skin_tone:"] = "\U0001f937\U0001f3fd\u200d\u2642\ufe0f",
[":man_shrugging::skin-tone-3:"] = "\U0001f937\U0001f3fd\u200d\u2642\ufe0f",
[":man_shrugging_tone4:"] = "\U0001f937\U0001f3fe\u200d\u2642\ufe0f",
[":man_shrugging_medium_dark_skin_tone:"] = "\U0001f937\U0001f3fe\u200d\u2642\ufe0f",
[":man_shrugging::skin-tone-4:"] = "\U0001f937\U0001f3fe\u200d\u2642\ufe0f",
[":man_shrugging_tone5:"] = "\U0001f937\U0001f3ff\u200d\u2642\ufe0f",
[":man_shrugging_dark_skin_tone:"] = "\U0001f937\U0001f3ff\u200d\u2642\ufe0f",
[":man_shrugging::skin-tone-5:"] = "\U0001f937\U0001f3ff\u200d\u2642\ufe0f",
[":man_singer:"] = "\U0001f468\u200d\U0001f3a4",
[":man_singer_tone1:"] = "\U0001f468\U0001f3fb\u200d\U0001f3a4",
[":man_singer_light_skin_tone:"] = "\U0001f468\U0001f3fb\u200d\U0001f3a4",
[":man_singer::skin-tone-1:"] = "\U0001f468\U0001f3fb\u200d\U0001f3a4",
[":man_singer_tone2:"] = "\U0001f468\U0001f3fc\u200d\U0001f3a4",
[":man_singer_medium_light_skin_tone:"] = "\U0001f468\U0001f3fc\u200d\U0001f3a4",
[":man_singer::skin-tone-2:"] = "\U0001f468\U0001f3fc\u200d\U0001f3a4",
[":man_singer_tone3:"] = "\U0001f468\U0001f3fd\u200d\U0001f3a4",
[":man_singer_medium_skin_tone:"] = "\U0001f468\U0001f3fd\u200d\U0001f3a4",
[":man_singer::skin-tone-3:"] = "\U0001f468\U0001f3fd\u200d\U0001f3a4",
[":man_singer_tone4:"] = "\U0001f468\U0001f3fe\u200d\U0001f3a4",
[":man_singer_medium_dark_skin_tone:"] = "\U0001f468\U0001f3fe\u200d\U0001f3a4",
[":man_singer::skin-tone-4:"] = "\U0001f468\U0001f3fe\u200d\U0001f3a4",
[":man_singer_tone5:"] = "\U0001f468\U0001f3ff\u200d\U0001f3a4",
[":man_singer_dark_skin_tone:"] = "\U0001f468\U0001f3ff\u200d\U0001f3a4",
[":man_singer::skin-tone-5:"] = "\U0001f468\U0001f3ff\u200d\U0001f3a4",
[":man_standing:"] = "\U0001f9cd\u200d\u2642\ufe0f",
[":man_standing_tone1:"] = "\U0001f9cd\U0001f3fb\u200d\u2642\ufe0f",
[":man_standing_light_skin_tone:"] = "\U0001f9cd\U0001f3fb\u200d\u2642\ufe0f",
[":man_standing::skin-tone-1:"] = "\U0001f9cd\U0001f3fb\u200d\u2642\ufe0f",
[":man_standing_tone2:"] = "\U0001f9cd\U0001f3fc\u200d\u2642\ufe0f",
[":man_standing_medium_light_skin_tone:"] = "\U0001f9cd\U0001f3fc\u200d\u2642\ufe0f",
[":man_standing::skin-tone-2:"] = "\U0001f9cd\U0001f3fc\u200d\u2642\ufe0f",
[":man_standing_tone3:"] = "\U0001f9cd\U0001f3fd\u200d\u2642\ufe0f",
[":man_standing_medium_skin_tone:"] = "\U0001f9cd\U0001f3fd\u200d\u2642\ufe0f",
[":man_standing::skin-tone-3:"] = "\U0001f9cd\U0001f3fd\u200d\u2642\ufe0f",
[":man_standing_tone4:"] = "\U0001f9cd\U0001f3fe\u200d\u2642\ufe0f",
[":man_standing_medium_dark_skin_tone:"] = "\U0001f9cd\U0001f3fe\u200d\u2642\ufe0f",
[":man_standing::skin-tone-4:"] = "\U0001f9cd\U0001f3fe\u200d\u2642\ufe0f",
[":man_standing_tone5:"] = "\U0001f9cd\U0001f3ff\u200d\u2642\ufe0f",
[":man_standing_dark_skin_tone:"] = "\U0001f9cd\U0001f3ff\u200d\u2642\ufe0f",
[":man_standing::skin-tone-5:"] = "\U0001f9cd\U0001f3ff\u200d\u2642\ufe0f",
[":man_student:"] = "\U0001f468\u200d\U0001f393",
[":man_student_tone1:"] = "\U0001f468\U0001f3fb\u200d\U0001f393",
[":man_student_light_skin_tone:"] = "\U0001f468\U0001f3fb\u200d\U0001f393",
[":man_student::skin-tone-1:"] = "\U0001f468\U0001f3fb\u200d\U0001f393",
[":man_student_tone2:"] = "\U0001f468\U0001f3fc\u200d\U0001f393",
[":man_student_medium_light_skin_tone:"] = "\U0001f468\U0001f3fc\u200d\U0001f393",
[":man_student::skin-tone-2:"] = "\U0001f468\U0001f3fc\u200d\U0001f393",
[":man_student_tone3:"] = "\U0001f468\U0001f3fd\u200d\U0001f393",
[":man_student_medium_skin_tone:"] = "\U0001f468\U0001f3fd\u200d\U0001f393",
[":man_student::skin-tone-3:"] = "\U0001f468\U0001f3fd\u200d\U0001f393",
[":man_student_tone4:"] = "\U0001f468\U0001f3fe\u200d\U0001f393",
[":man_student_medium_dark_skin_tone:"] = "\U0001f468\U0001f3fe\u200d\U0001f393",
[":man_student::skin-tone-4:"] = "\U0001f468\U0001f3fe\u200d\U0001f393",
[":man_student_tone5:"] = "\U0001f468\U0001f3ff\u200d\U0001f393",
[":man_student_dark_skin_tone:"] = "\U0001f468\U0001f3ff\u200d\U0001f393",
[":man_student::skin-tone-5:"] = "\U0001f468\U0001f3ff\u200d\U0001f393",
[":man_superhero:"] = "\U0001f9b8\u200d\u2642\ufe0f",
[":man_superhero_tone1:"] = "\U0001f9b8\U0001f3fb\u200d\u2642\ufe0f",
[":man_superhero_light_skin_tone:"] = "\U0001f9b8\U0001f3fb\u200d\u2642\ufe0f",
[":man_superhero::skin-tone-1:"] = "\U0001f9b8\U0001f3fb\u200d\u2642\ufe0f",
[":man_superhero_tone2:"] = "\U0001f9b8\U0001f3fc\u200d\u2642\ufe0f",
[":man_superhero_medium_light_skin_tone:"] = "\U0001f9b8\U0001f3fc\u200d\u2642\ufe0f",
[":man_superhero::skin-tone-2:"] = "\U0001f9b8\U0001f3fc\u200d\u2642\ufe0f",
[":man_superhero_tone3:"] = "\U0001f9b8\U0001f3fd\u200d\u2642\ufe0f",
[":man_superhero_medium_skin_tone:"] = "\U0001f9b8\U0001f3fd\u200d\u2642\ufe0f",
[":man_superhero::skin-tone-3:"] = "\U0001f9b8\U0001f3fd\u200d\u2642\ufe0f",
[":man_superhero_tone4:"] = "\U0001f9b8\U0001f3fe\u200d\u2642\ufe0f",
[":man_superhero_medium_dark_skin_tone:"] = "\U0001f9b8\U0001f3fe\u200d\u2642\ufe0f",
[":man_superhero::skin-tone-4:"] = "\U0001f9b8\U0001f3fe\u200d\u2642\ufe0f",
[":man_superhero_tone5:"] = "\U0001f9b8\U0001f3ff\u200d\u2642\ufe0f",
[":man_superhero_dark_skin_tone:"] = "\U0001f9b8\U0001f3ff\u200d\u2642\ufe0f",
[":man_superhero::skin-tone-5:"] = "\U0001f9b8\U0001f3ff\u200d\u2642\ufe0f",
[":man_supervillain:"] = "\U0001f9b9\u200d\u2642\ufe0f",
[":man_supervillain_tone1:"] = "\U0001f9b9\U0001f3fb\u200d\u2642\ufe0f",
[":man_supervillain_light_skin_tone:"] = "\U0001f9b9\U0001f3fb\u200d\u2642\ufe0f",
[":man_supervillain::skin-tone-1:"] = "\U0001f9b9\U0001f3fb\u200d\u2642\ufe0f",
[":man_supervillain_tone2:"] = "\U0001f9b9\U0001f3fc\u200d\u2642\ufe0f",
[":man_supervillain_medium_light_skin_tone:"] = "\U0001f9b9\U0001f3fc\u200d\u2642\ufe0f",
[":man_supervillain::skin-tone-2:"] = "\U0001f9b9\U0001f3fc\u200d\u2642\ufe0f",
[":man_supervillain_tone3:"] = "\U0001f9b9\U0001f3fd\u200d\u2642\ufe0f",
[":man_supervillain_medium_skin_tone:"] = "\U0001f9b9\U0001f3fd\u200d\u2642\ufe0f",
[":man_supervillain::skin-tone-3:"] = "\U0001f9b9\U0001f3fd\u200d\u2642\ufe0f",
[":man_supervillain_tone4:"] = "\U0001f9b9\U0001f3fe\u200d\u2642\ufe0f",
[":man_supervillain_medium_dark_skin_tone:"] = "\U0001f9b9\U0001f3fe\u200d\u2642\ufe0f",
[":man_supervillain::skin-tone-4:"] = "\U0001f9b9\U0001f3fe\u200d\u2642\ufe0f",
[":man_supervillain_tone5:"] = "\U0001f9b9\U0001f3ff\u200d\u2642\ufe0f",
[":man_supervillain_dark_skin_tone:"] = "\U0001f9b9\U0001f3ff\u200d\u2642\ufe0f",
[":man_supervillain::skin-tone-5:"] = "\U0001f9b9\U0001f3ff\u200d\u2642\ufe0f",
[":man_surfing:"] = "\U0001f3c4\u200d\u2642\ufe0f",
[":man_surfing_tone1:"] = "\U0001f3c4\U0001f3fb\u200d\u2642\ufe0f",
[":man_surfing_light_skin_tone:"] = "\U0001f3c4\U0001f3fb\u200d\u2642\ufe0f",
[":man_surfing::skin-tone-1:"] = "\U0001f3c4\U0001f3fb\u200d\u2642\ufe0f",
[":man_surfing_tone2:"] = "\U0001f3c4\U0001f3fc\u200d\u2642\ufe0f",
[":man_surfing_medium_light_skin_tone:"] = "\U0001f3c4\U0001f3fc\u200d\u2642\ufe0f",
[":man_surfing::skin-tone-2:"] = "\U0001f3c4\U0001f3fc\u200d\u2642\ufe0f",
[":man_surfing_tone3:"] = "\U0001f3c4\U0001f3fd\u200d\u2642\ufe0f",
[":man_surfing_medium_skin_tone:"] = "\U0001f3c4\U0001f3fd\u200d\u2642\ufe0f",
[":man_surfing::skin-tone-3:"] = "\U0001f3c4\U0001f3fd\u200d\u2642\ufe0f",
[":man_surfing_tone4:"] = "\U0001f3c4\U0001f3fe\u200d\u2642\ufe0f",
[":man_surfing_medium_dark_skin_tone:"] = "\U0001f3c4\U0001f3fe\u200d\u2642\ufe0f",
[":man_surfing::skin-tone-4:"] = "\U0001f3c4\U0001f3fe\u200d\u2642\ufe0f",
[":man_surfing_tone5:"] = "\U0001f3c4\U0001f3ff\u200d\u2642\ufe0f",
[":man_surfing_dark_skin_tone:"] = "\U0001f3c4\U0001f3ff\u200d\u2642\ufe0f",
[":man_surfing::skin-tone-5:"] = "\U0001f3c4\U0001f3ff\u200d\u2642\ufe0f",
[":man_swimming:"] = "\U0001f3ca\u200d\u2642\ufe0f",
[":man_swimming_tone1:"] = "\U0001f3ca\U0001f3fb\u200d\u2642\ufe0f",
[":man_swimming_light_skin_tone:"] = "\U0001f3ca\U0001f3fb\u200d\u2642\ufe0f",
[":man_swimming::skin-tone-1:"] = "\U0001f3ca\U0001f3fb\u200d\u2642\ufe0f",
[":man_swimming_tone2:"] = "\U0001f3ca\U0001f3fc\u200d\u2642\ufe0f",
[":man_swimming_medium_light_skin_tone:"] = "\U0001f3ca\U0001f3fc\u200d\u2642\ufe0f",
[":man_swimming::skin-tone-2:"] = "\U0001f3ca\U0001f3fc\u200d\u2642\ufe0f",
[":man_swimming_tone3:"] = "\U0001f3ca\U0001f3fd\u200d\u2642\ufe0f",
[":man_swimming_medium_skin_tone:"] = "\U0001f3ca\U0001f3fd\u200d\u2642\ufe0f",
[":man_swimming::skin-tone-3:"] = "\U0001f3ca\U0001f3fd\u200d\u2642\ufe0f",
[":man_swimming_tone4:"] = "\U0001f3ca\U0001f3fe\u200d\u2642\ufe0f",
[":man_swimming_medium_dark_skin_tone:"] = "\U0001f3ca\U0001f3fe\u200d\u2642\ufe0f",
[":man_swimming::skin-tone-4:"] = "\U0001f3ca\U0001f3fe\u200d\u2642\ufe0f",
[":man_swimming_tone5:"] = "\U0001f3ca\U0001f3ff\u200d\u2642\ufe0f",
[":man_swimming_dark_skin_tone:"] = "\U0001f3ca\U0001f3ff\u200d\u2642\ufe0f",
[":man_swimming::skin-tone-5:"] = "\U0001f3ca\U0001f3ff\u200d\u2642\ufe0f",
[":man_teacher:"] = "\U0001f468\u200d\U0001f3eb",
[":man_teacher_tone1:"] = "\U0001f468\U0001f3fb\u200d\U0001f3eb",
[":man_teacher_light_skin_tone:"] = "\U0001f468\U0001f3fb\u200d\U0001f3eb",
[":man_teacher::skin-tone-1:"] = "\U0001f468\U0001f3fb\u200d\U0001f3eb",
[":man_teacher_tone2:"] = "\U0001f468\U0001f3fc\u200d\U0001f3eb",
[":man_teacher_medium_light_skin_tone:"] = "\U0001f468\U0001f3fc\u200d\U0001f3eb",
[":man_teacher::skin-tone-2:"] = "\U0001f468\U0001f3fc\u200d\U0001f3eb",
[":man_teacher_tone3:"] = "\U0001f468\U0001f3fd\u200d\U0001f3eb",
[":man_teacher_medium_skin_tone:"] = "\U0001f468\U0001f3fd\u200d\U0001f3eb",
[":man_teacher::skin-tone-3:"] = "\U0001f468\U0001f3fd\u200d\U0001f3eb",
[":man_teacher_tone4:"] = "\U0001f468\U0001f3fe\u200d\U0001f3eb",
[":man_teacher_medium_dark_skin_tone:"] = "\U0001f468\U0001f3fe\u200d\U0001f3eb",
[":man_teacher::skin-tone-4:"] = "\U0001f468\U0001f3fe\u200d\U0001f3eb",
[":man_teacher_tone5:"] = "\U0001f468\U0001f3ff\u200d\U0001f3eb",
[":man_teacher_dark_skin_tone:"] = "\U0001f468\U0001f3ff\u200d\U0001f3eb",
[":man_teacher::skin-tone-5:"] = "\U0001f468\U0001f3ff\u200d\U0001f3eb",
[":man_technologist:"] = "\U0001f468\u200d\U0001f4bb",
[":man_technologist_tone1:"] = "\U0001f468\U0001f3fb\u200d\U0001f4bb",
[":man_technologist_light_skin_tone:"] = "\U0001f468\U0001f3fb\u200d\U0001f4bb",
[":man_technologist::skin-tone-1:"] = "\U0001f468\U0001f3fb\u200d\U0001f4bb",
[":man_technologist_tone2:"] = "\U0001f468\U0001f3fc\u200d\U0001f4bb",
[":man_technologist_medium_light_skin_tone:"] = "\U0001f468\U0001f3fc\u200d\U0001f4bb",
[":man_technologist::skin-tone-2:"] = "\U0001f468\U0001f3fc\u200d\U0001f4bb",
[":man_technologist_tone3:"] = "\U0001f468\U0001f3fd\u200d\U0001f4bb",
[":man_technologist_medium_skin_tone:"] = "\U0001f468\U0001f3fd\u200d\U0001f4bb",
[":man_technologist::skin-tone-3:"] = "\U0001f468\U0001f3fd\u200d\U0001f4bb",
[":man_technologist_tone4:"] = "\U0001f468\U0001f3fe\u200d\U0001f4bb",
[":man_technologist_medium_dark_skin_tone:"] = "\U0001f468\U0001f3fe\u200d\U0001f4bb",
[":man_technologist::skin-tone-4:"] = "\U0001f468\U0001f3fe\u200d\U0001f4bb",
[":man_technologist_tone5:"] = "\U0001f468\U0001f3ff\u200d\U0001f4bb",
[":man_technologist_dark_skin_tone:"] = "\U0001f468\U0001f3ff\u200d\U0001f4bb",
[":man_technologist::skin-tone-5:"] = "\U0001f468\U0001f3ff\u200d\U0001f4bb",
[":man_tipping_hand:"] = "\U0001f481\u200d\u2642\ufe0f",
[":man_tipping_hand_tone1:"] = "\U0001f481\U0001f3fb\u200d\u2642\ufe0f",
[":man_tipping_hand_light_skin_tone:"] = "\U0001f481\U0001f3fb\u200d\u2642\ufe0f",
[":man_tipping_hand::skin-tone-1:"] = "\U0001f481\U0001f3fb\u200d\u2642\ufe0f",
[":man_tipping_hand_tone2:"] = "\U0001f481\U0001f3fc\u200d\u2642\ufe0f",
[":man_tipping_hand_medium_light_skin_tone:"] = "\U0001f481\U0001f3fc\u200d\u2642\ufe0f",
[":man_tipping_hand::skin-tone-2:"] = "\U0001f481\U0001f3fc\u200d\u2642\ufe0f",
[":man_tipping_hand_tone3:"] = "\U0001f481\U0001f3fd\u200d\u2642\ufe0f",
[":man_tipping_hand_medium_skin_tone:"] = "\U0001f481\U0001f3fd\u200d\u2642\ufe0f",
[":man_tipping_hand::skin-tone-3:"] = "\U0001f481\U0001f3fd\u200d\u2642\ufe0f",
[":man_tipping_hand_tone4:"] = "\U0001f481\U0001f3fe\u200d\u2642\ufe0f",
[":man_tipping_hand_medium_dark_skin_tone:"] = "\U0001f481\U0001f3fe\u200d\u2642\ufe0f",
[":man_tipping_hand::skin-tone-4:"] = "\U0001f481\U0001f3fe\u200d\u2642\ufe0f",
[":man_tipping_hand_tone5:"] = "\U0001f481\U0001f3ff\u200d\u2642\ufe0f",
[":man_tipping_hand_dark_skin_tone:"] = "\U0001f481\U0001f3ff\u200d\u2642\ufe0f",
[":man_tipping_hand::skin-tone-5:"] = "\U0001f481\U0001f3ff\u200d\u2642\ufe0f",
[":man_tone1:"] = "\U0001f468\U0001f3fb",
[":man::skin-tone-1:"] = "\U0001f468\U0001f3fb",
[":man_tone2:"] = "\U0001f468\U0001f3fc",
[":man::skin-tone-2:"] = "\U0001f468\U0001f3fc",
[":man_tone3:"] = "\U0001f468\U0001f3fd",
[":man::skin-tone-3:"] = "\U0001f468\U0001f3fd",
[":man_tone4:"] = "\U0001f468\U0001f3fe",
[":man::skin-tone-4:"] = "\U0001f468\U0001f3fe",
[":man_tone5:"] = "\U0001f468\U0001f3ff",
[":man::skin-tone-5:"] = "\U0001f468\U0001f3ff",
[":man_vampire:"] = "\U0001f9db\u200d\u2642\ufe0f",
[":man_vampire_tone1:"] = "\U0001f9db\U0001f3fb\u200d\u2642\ufe0f",
[":man_vampire_light_skin_tone:"] = "\U0001f9db\U0001f3fb\u200d\u2642\ufe0f",
[":man_vampire::skin-tone-1:"] = "\U0001f9db\U0001f3fb\u200d\u2642\ufe0f",
[":man_vampire_tone2:"] = "\U0001f9db\U0001f3fc\u200d\u2642\ufe0f",
[":man_vampire_medium_light_skin_tone:"] = "\U0001f9db\U0001f3fc\u200d\u2642\ufe0f",
[":man_vampire::skin-tone-2:"] = "\U0001f9db\U0001f3fc\u200d\u2642\ufe0f",
[":man_vampire_tone3:"] = "\U0001f9db\U0001f3fd\u200d\u2642\ufe0f",
[":man_vampire_medium_skin_tone:"] = "\U0001f9db\U0001f3fd\u200d\u2642\ufe0f",
[":man_vampire::skin-tone-3:"] = "\U0001f9db\U0001f3fd\u200d\u2642\ufe0f",
[":man_vampire_tone4:"] = "\U0001f9db\U0001f3fe\u200d\u2642\ufe0f",
[":man_vampire_medium_dark_skin_tone:"] = "\U0001f9db\U0001f3fe\u200d\u2642\ufe0f",
[":man_vampire::skin-tone-4:"] = "\U0001f9db\U0001f3fe\u200d\u2642\ufe0f",
[":man_vampire_tone5:"] = "\U0001f9db\U0001f3ff\u200d\u2642\ufe0f",
[":man_vampire_dark_skin_tone:"] = "\U0001f9db\U0001f3ff\u200d\u2642\ufe0f",
[":man_vampire::skin-tone-5:"] = "\U0001f9db\U0001f3ff\u200d\u2642\ufe0f",
[":man_walking:"] = "\U0001f6b6\u200d\u2642\ufe0f",
[":man_walking_tone1:"] = "\U0001f6b6\U0001f3fb\u200d\u2642\ufe0f",
[":man_walking_light_skin_tone:"] = "\U0001f6b6\U0001f3fb\u200d\u2642\ufe0f",
[":man_walking::skin-tone-1:"] = "\U0001f6b6\U0001f3fb\u200d\u2642\ufe0f",
[":man_walking_tone2:"] = "\U0001f6b6\U0001f3fc\u200d\u2642\ufe0f",
[":man_walking_medium_light_skin_tone:"] = "\U0001f6b6\U0001f3fc\u200d\u2642\ufe0f",
[":man_walking::skin-tone-2:"] = "\U0001f6b6\U0001f3fc\u200d\u2642\ufe0f",
[":man_walking_tone3:"] = "\U0001f6b6\U0001f3fd\u200d\u2642\ufe0f",
[":man_walking_medium_skin_tone:"] = "\U0001f6b6\U0001f3fd\u200d\u2642\ufe0f",
[":man_walking::skin-tone-3:"] = "\U0001f6b6\U0001f3fd\u200d\u2642\ufe0f",
[":man_walking_tone4:"] = "\U0001f6b6\U0001f3fe\u200d\u2642\ufe0f",
[":man_walking_medium_dark_skin_tone:"] = "\U0001f6b6\U0001f3fe\u200d\u2642\ufe0f",
[":man_walking::skin-tone-4:"] = "\U0001f6b6\U0001f3fe\u200d\u2642\ufe0f",
[":man_walking_tone5:"] = "\U0001f6b6\U0001f3ff\u200d\u2642\ufe0f",
[":man_walking_dark_skin_tone:"] = "\U0001f6b6\U0001f3ff\u200d\u2642\ufe0f",
[":man_walking::skin-tone-5:"] = "\U0001f6b6\U0001f3ff\u200d\u2642\ufe0f",
[":man_wearing_turban:"] = "\U0001f473\u200d\u2642\ufe0f",
[":man_wearing_turban_tone1:"] = "\U0001f473\U0001f3fb\u200d\u2642\ufe0f",
[":man_wearing_turban_light_skin_tone:"] = "\U0001f473\U0001f3fb\u200d\u2642\ufe0f",
[":man_wearing_turban::skin-tone-1:"] = "\U0001f473\U0001f3fb\u200d\u2642\ufe0f",
[":man_wearing_turban_tone2:"] = "\U0001f473\U0001f3fc\u200d\u2642\ufe0f",
[":man_wearing_turban_medium_light_skin_tone:"] = "\U0001f473\U0001f3fc\u200d\u2642\ufe0f",
[":man_wearing_turban::skin-tone-2:"] = "\U0001f473\U0001f3fc\u200d\u2642\ufe0f",
[":man_wearing_turban_tone3:"] = "\U0001f473\U0001f3fd\u200d\u2642\ufe0f",
[":man_wearing_turban_medium_skin_tone:"] = "\U0001f473\U0001f3fd\u200d\u2642\ufe0f",
[":man_wearing_turban::skin-tone-3:"] = "\U0001f473\U0001f3fd\u200d\u2642\ufe0f",
[":man_wearing_turban_tone4:"] = "\U0001f473\U0001f3fe\u200d\u2642\ufe0f",
[":man_wearing_turban_medium_dark_skin_tone:"] = "\U0001f473\U0001f3fe\u200d\u2642\ufe0f",
[":man_wearing_turban::skin-tone-4:"] = "\U0001f473\U0001f3fe\u200d\u2642\ufe0f",
[":man_wearing_turban_tone5:"] = "\U0001f473\U0001f3ff\u200d\u2642\ufe0f",
[":man_wearing_turban_dark_skin_tone:"] = "\U0001f473\U0001f3ff\u200d\u2642\ufe0f",
[":man_wearing_turban::skin-tone-5:"] = "\U0001f473\U0001f3ff\u200d\u2642\ufe0f",
[":man_white_haired:"] = "\U0001f468\u200d\U0001f9b3",
[":man_white_haired_tone1:"] = "\U0001f468\U0001f3fb\u200d\U0001f9b3",
[":man_white_haired_light_skin_tone:"] = "\U0001f468\U0001f3fb\u200d\U0001f9b3",
[":man_white_haired::skin-tone-1:"] = "\U0001f468\U0001f3fb\u200d\U0001f9b3",
[":man_white_haired_tone2:"] = "\U0001f468\U0001f3fc\u200d\U0001f9b3",
[":man_white_haired_medium_light_skin_tone:"] = "\U0001f468\U0001f3fc\u200d\U0001f9b3",
[":man_white_haired::skin-tone-2:"] = "\U0001f468\U0001f3fc\u200d\U0001f9b3",
[":man_white_haired_tone3:"] = "\U0001f468\U0001f3fd\u200d\U0001f9b3",
[":man_white_haired_medium_skin_tone:"] = "\U0001f468\U0001f3fd\u200d\U0001f9b3",
[":man_white_haired::skin-tone-3:"] = "\U0001f468\U0001f3fd\u200d\U0001f9b3",
[":man_white_haired_tone4:"] = "\U0001f468\U0001f3fe\u200d\U0001f9b3",
[":man_white_haired_medium_dark_skin_tone:"] = "\U0001f468\U0001f3fe\u200d\U0001f9b3",
[":man_white_haired::skin-tone-4:"] = "\U0001f468\U0001f3fe\u200d\U0001f9b3",
[":man_white_haired_tone5:"] = "\U0001f468\U0001f3ff\u200d\U0001f9b3",
[":man_white_haired_dark_skin_tone:"] = "\U0001f468\U0001f3ff\u200d\U0001f9b3",
[":man_white_haired::skin-tone-5:"] = "\U0001f468\U0001f3ff\u200d\U0001f9b3",
[":man_with_chinese_cap:"] = "\U0001f472",
[":man_with_gua_pi_mao:"] = "\U0001f472",
[":man_with_chinese_cap_tone1:"] = "\U0001f472\U0001f3fb",
[":man_with_gua_pi_mao_tone1:"] = "\U0001f472\U0001f3fb",
[":man_with_chinese_cap::skin-tone-1:"] = "\U0001f472\U0001f3fb",
[":man_with_gua_pi_mao::skin-tone-1:"] = "\U0001f472\U0001f3fb",
[":man_with_chinese_cap_tone2:"] = "\U0001f472\U0001f3fc",
[":man_with_gua_pi_mao_tone2:"] = "\U0001f472\U0001f3fc",
[":man_with_chinese_cap::skin-tone-2:"] = "\U0001f472\U0001f3fc",
[":man_with_gua_pi_mao::skin-tone-2:"] = "\U0001f472\U0001f3fc",
[":man_with_chinese_cap_tone3:"] = "\U0001f472\U0001f3fd",
[":man_with_gua_pi_mao_tone3:"] = "\U0001f472\U0001f3fd",
[":man_with_chinese_cap::skin-tone-3:"] = "\U0001f472\U0001f3fd",
[":man_with_gua_pi_mao::skin-tone-3:"] = "\U0001f472\U0001f3fd",
[":man_with_chinese_cap_tone4:"] = "\U0001f472\U0001f3fe",
[":man_with_gua_pi_mao_tone4:"] = "\U0001f472\U0001f3fe",
[":man_with_chinese_cap::skin-tone-4:"] = "\U0001f472\U0001f3fe",
[":man_with_gua_pi_mao::skin-tone-4:"] = "\U0001f472\U0001f3fe",
[":man_with_chinese_cap_tone5:"] = "\U0001f472\U0001f3ff",
[":man_with_gua_pi_mao_tone5:"] = "\U0001f472\U0001f3ff",
[":man_with_chinese_cap::skin-tone-5:"] = "\U0001f472\U0001f3ff",
[":man_with_gua_pi_mao::skin-tone-5:"] = "\U0001f472\U0001f3ff",
[":man_with_probing_cane:"] = "\U0001f468\u200d\U0001f9af",
[":man_with_probing_cane_tone1:"] = "\U0001f468\U0001f3fb\u200d\U0001f9af",
[":man_with_probing_cane_light_skin_tone:"] = "\U0001f468\U0001f3fb\u200d\U0001f9af",
[":man_with_probing_cane::skin-tone-1:"] = "\U0001f468\U0001f3fb\u200d\U0001f9af",
[":man_with_probing_cane_tone2:"] = "\U0001f468\U0001f3fc\u200d\U0001f9af",
[":man_with_probing_cane_medium_light_skin_tone:"] = "\U0001f468\U0001f3fc\u200d\U0001f9af",
[":man_with_probing_cane::skin-tone-2:"] = "\U0001f468\U0001f3fc\u200d\U0001f9af",
[":man_with_probing_cane_tone3:"] = "\U0001f468\U0001f3fd\u200d\U0001f9af",
[":man_with_probing_cane_medium_skin_tone:"] = "\U0001f468\U0001f3fd\u200d\U0001f9af",
[":man_with_probing_cane::skin-tone-3:"] = "\U0001f468\U0001f3fd\u200d\U0001f9af",
[":man_with_probing_cane_tone4:"] = "\U0001f468\U0001f3fe\u200d\U0001f9af",
[":man_with_probing_cane_medium_dark_skin_tone:"] = "\U0001f468\U0001f3fe\u200d\U0001f9af",
[":man_with_probing_cane::skin-tone-4:"] = "\U0001f468\U0001f3fe\u200d\U0001f9af",
[":man_with_probing_cane_tone5:"] = "\U0001f468\U0001f3ff\u200d\U0001f9af",
[":man_with_probing_cane_dark_skin_tone:"] = "\U0001f468\U0001f3ff\u200d\U0001f9af",
[":man_with_probing_cane::skin-tone-5:"] = "\U0001f468\U0001f3ff\u200d\U0001f9af",
[":man_with_veil:"] = "\U0001f470\u200d\u2642\ufe0f",
[":man_with_veil_tone1:"] = "\U0001f470\U0001f3fb\u200d\u2642\ufe0f",
[":man_with_veil_light_skin_tone:"] = "\U0001f470\U0001f3fb\u200d\u2642\ufe0f",
[":man_with_veil::skin-tone-1:"] = "\U0001f470\U0001f3fb\u200d\u2642\ufe0f",
[":man_with_veil_tone2:"] = "\U0001f470\U0001f3fc\u200d\u2642\ufe0f",
[":man_with_veil_medium_light_skin_tone:"] = "\U0001f470\U0001f3fc\u200d\u2642\ufe0f",
[":man_with_veil::skin-tone-2:"] = "\U0001f470\U0001f3fc\u200d\u2642\ufe0f",
[":man_with_veil_tone3:"] = "\U0001f470\U0001f3fd\u200d\u2642\ufe0f",
[":man_with_veil_medium_skin_tone:"] = "\U0001f470\U0001f3fd\u200d\u2642\ufe0f",
[":man_with_veil::skin-tone-3:"] = "\U0001f470\U0001f3fd\u200d\u2642\ufe0f",
[":man_with_veil_tone4:"] = "\U0001f470\U0001f3fe\u200d\u2642\ufe0f",
[":man_with_veil_medium_dark_skin_tone:"] = "\U0001f470\U0001f3fe\u200d\u2642\ufe0f",
[":man_with_veil::skin-tone-4:"] = "\U0001f470\U0001f3fe\u200d\u2642\ufe0f",
[":man_with_veil_tone5:"] = "\U0001f470\U0001f3ff\u200d\u2642\ufe0f",
[":man_with_veil_dark_skin_tone:"] = "\U0001f470\U0001f3ff\u200d\u2642\ufe0f",
[":man_with_veil::skin-tone-5:"] = "\U0001f470\U0001f3ff\u200d\u2642\ufe0f",
[":man_zombie:"] = "\U0001f9df\u200d\u2642\ufe0f",
[":mango:"] = "\U0001f96d",
[":mans_shoe:"] = "\U0001f45e",
[":manual_wheelchair:"] = "\U0001f9bd",
[":map:"] = "\U0001f5fa\ufe0f",
[":world_map:"] = "\U0001f5fa\ufe0f",
[":maple_leaf:"] = "\U0001f341",
[":martial_arts_uniform:"] = "\U0001f94b",
[":karate_uniform:"] = "\U0001f94b",
[":mask:"] = "\U0001f637",
[":mate:"] = "\U0001f9c9",
[":meat_on_bone:"] = "\U0001f356",
[":mechanic:"] = "\U0001f9d1\u200d\U0001f527",
[":mechanic_tone1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f527",
[":mechanic_light_skin_tone:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f527",
[":mechanic::skin-tone-1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f527",
[":mechanic_tone2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f527",
[":mechanic_medium_light_skin_tone:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f527",
[":mechanic::skin-tone-2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f527",
[":mechanic_tone3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f527",
[":mechanic_medium_skin_tone:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f527",
[":mechanic::skin-tone-3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f527",
[":mechanic_tone4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f527",
[":mechanic_medium_dark_skin_tone:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f527",
[":mechanic::skin-tone-4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f527",
[":mechanic_tone5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f527",
[":mechanic_dark_skin_tone:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f527",
[":mechanic::skin-tone-5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f527",
[":mechanical_arm:"] = "\U0001f9be",
[":mechanical_leg:"] = "\U0001f9bf",
[":medal:"] = "\U0001f3c5",
[":sports_medal:"] = "\U0001f3c5",
[":medical_symbol:"] = "\u2695\ufe0f",
[":mega:"] = "\U0001f4e3",
[":melon:"] = "\U0001f348",
[":men_holding_hands_tone1:"] = "\U0001f46c",
[":men_holding_hands_light_skin_tone:"] = "\U0001f46c",
[":men_holding_hands_tone1_tone2:"] = "\U0001f46c",
[":men_holding_hands_light_skin_tone_medium_light_skin_tone:"] = "\U0001f46c",
[":men_holding_hands_tone1_tone3:"] = "\U0001f46c",
[":men_holding_hands_light_skin_tone_medium_skin_tone:"] = "\U0001f46c",
[":men_holding_hands_tone1_tone4:"] = "\U0001f46c",
[":men_holding_hands_light_skin_tone_medium_dark_skin_tone:"] = "\U0001f46c",
[":men_holding_hands_tone1_tone5:"] = "\U0001f46c",
[":men_holding_hands_light_skin_tone_dark_skin_tone:"] = "\U0001f46c",
[":men_holding_hands_tone2:"] = "\U0001f46c",
[":men_holding_hands_medium_light_skin_tone:"] = "\U0001f46c",
[":men_holding_hands_tone2_tone1:"] = "\U0001f46c",
[":men_holding_hands_medium_light_skin_tone_light_skin_tone:"] = "\U0001f46c",
[":men_holding_hands_tone2_tone3:"] = "\U0001f46c",
[":men_holding_hands_medium_light_skin_tone_medium_skin_tone:"] = "\U0001f46c",
[":men_holding_hands_tone2_tone4:"] = "\U0001f46c",
[":men_holding_hands_medium_light_skin_tone_medium_dark_skin_tone:"] = "\U0001f46c",
[":men_holding_hands_tone2_tone5:"] = "\U0001f46c",
[":men_holding_hands_medium_light_skin_tone_dark_skin_tone:"] = "\U0001f46c",
[":men_holding_hands_tone3:"] = "\U0001f46c",
[":men_holding_hands_medium_skin_tone:"] = "\U0001f46c",
[":men_holding_hands_tone3_tone1:"] = "\U0001f46c",
[":men_holding_hands_medium_skin_tone_light_skin_tone:"] = "\U0001f46c",
[":men_holding_hands_tone3_tone2:"] = "\U0001f46c",
[":men_holding_hands_medium_skin_tone_medium_light_skin_tone:"] = "\U0001f46c",
[":men_holding_hands_tone3_tone4:"] = "\U0001f46c",
[":men_holding_hands_medium_skin_tone_medium_dark_skin_tone:"] = "\U0001f46c",
[":men_holding_hands_tone3_tone5:"] = "\U0001f46c",
[":men_holding_hands_medium_skin_tone_dark_skin_tone:"] = "\U0001f46c",
[":men_holding_hands_tone4:"] = "\U0001f46c",
[":men_holding_hands_medium_dark_skin_tone:"] = "\U0001f46c",
[":men_holding_hands_tone4_tone1:"] = "\U0001f46c",
[":men_holding_hands_medium_dark_skin_tone_light_skin_tone:"] = "\U0001f46c",
[":men_holding_hands_tone4_tone2:"] = "\U0001f46c",
[":men_holding_hands_medium_dark_skin_tone_medium_light_skin_tone:"] = "\U0001f46c",
[":men_holding_hands_tone4_tone3:"] = "\U0001f46c",
[":men_holding_hands_medium_dark_skin_tone_medium_skin_tone:"] = "\U0001f46c",
[":men_holding_hands_tone4_tone5:"] = "\U0001f46c",
[":men_holding_hands_medium_dark_skin_tone_dark_skin_tone:"] = "\U0001f46c",
[":men_holding_hands_tone5:"] = "\U0001f46c",
[":men_holding_hands_dark_skin_tone:"] = "\U0001f46c",
[":men_holding_hands_tone5_tone1:"] = "\U0001f46c",
[":men_holding_hands_dark_skin_tone_light_skin_tone:"] = "\U0001f46c",
[":men_holding_hands_tone5_tone2:"] = "\U0001f46c",
[":men_holding_hands_dark_skin_tone_medium_light_skin_tone:"] = "\U0001f46c",
[":men_holding_hands_tone5_tone3:"] = "\U0001f46c",
[":men_holding_hands_dark_skin_tone_medium_skin_tone:"] = "\U0001f46c",
[":men_holding_hands_tone5_tone4:"] = "\U0001f46c",
[":men_holding_hands_dark_skin_tone_medium_dark_skin_tone:"] = "\U0001f46c",
[":men_with_bunny_ears_partying:"] = "\U0001f46f\u200d\u2642\ufe0f",
[":men_wrestling:"] = "\U0001f93c\u200d\u2642\ufe0f",
[":menorah:"] = "\U0001f54e",
[":mens:"] = "\U0001f6b9",
[":mermaid:"] = "\U0001f9dc\u200d\u2640\ufe0f",
[":mermaid_tone1:"] = "\U0001f9dc\U0001f3fb\u200d\u2640\ufe0f",
[":mermaid_light_skin_tone:"] = "\U0001f9dc\U0001f3fb\u200d\u2640\ufe0f",
[":mermaid::skin-tone-1:"] = "\U0001f9dc\U0001f3fb\u200d\u2640\ufe0f",
[":mermaid_tone2:"] = "\U0001f9dc\U0001f3fc\u200d\u2640\ufe0f",
[":mermaid_medium_light_skin_tone:"] = "\U0001f9dc\U0001f3fc\u200d\u2640\ufe0f",
[":mermaid::skin-tone-2:"] = "\U0001f9dc\U0001f3fc\u200d\u2640\ufe0f",
[":mermaid_tone3:"] = "\U0001f9dc\U0001f3fd\u200d\u2640\ufe0f",
[":mermaid_medium_skin_tone:"] = "\U0001f9dc\U0001f3fd\u200d\u2640\ufe0f",
[":mermaid::skin-tone-3:"] = "\U0001f9dc\U0001f3fd\u200d\u2640\ufe0f",
[":mermaid_tone4:"] = "\U0001f9dc\U0001f3fe\u200d\u2640\ufe0f",
[":mermaid_medium_dark_skin_tone:"] = "\U0001f9dc\U0001f3fe\u200d\u2640\ufe0f",
[":mermaid::skin-tone-4:"] = "\U0001f9dc\U0001f3fe\u200d\u2640\ufe0f",
[":mermaid_tone5:"] = "\U0001f9dc\U0001f3ff\u200d\u2640\ufe0f",
[":mermaid_dark_skin_tone:"] = "\U0001f9dc\U0001f3ff\u200d\u2640\ufe0f",
[":mermaid::skin-tone-5:"] = "\U0001f9dc\U0001f3ff\u200d\u2640\ufe0f",
[":merman:"] = "\U0001f9dc\u200d\u2642\ufe0f",
[":merman_tone1:"] = "\U0001f9dc\U0001f3fb\u200d\u2642\ufe0f",
[":merman_light_skin_tone:"] = "\U0001f9dc\U0001f3fb\u200d\u2642\ufe0f",
[":merman::skin-tone-1:"] = "\U0001f9dc\U0001f3fb\u200d\u2642\ufe0f",
[":merman_tone2:"] = "\U0001f9dc\U0001f3fc\u200d\u2642\ufe0f",
[":merman_medium_light_skin_tone:"] = "\U0001f9dc\U0001f3fc\u200d\u2642\ufe0f",
[":merman::skin-tone-2:"] = "\U0001f9dc\U0001f3fc\u200d\u2642\ufe0f",
[":merman_tone3:"] = "\U0001f9dc\U0001f3fd\u200d\u2642\ufe0f",
[":merman_medium_skin_tone:"] = "\U0001f9dc\U0001f3fd\u200d\u2642\ufe0f",
[":merman::skin-tone-3:"] = "\U0001f9dc\U0001f3fd\u200d\u2642\ufe0f",
[":merman_tone4:"] = "\U0001f9dc\U0001f3fe\u200d\u2642\ufe0f",
[":merman_medium_dark_skin_tone:"] = "\U0001f9dc\U0001f3fe\u200d\u2642\ufe0f",
[":merman::skin-tone-4:"] = "\U0001f9dc\U0001f3fe\u200d\u2642\ufe0f",
[":merman_tone5:"] = "\U0001f9dc\U0001f3ff\u200d\u2642\ufe0f",
[":merman_dark_skin_tone:"] = "\U0001f9dc\U0001f3ff\u200d\u2642\ufe0f",
[":merman::skin-tone-5:"] = "\U0001f9dc\U0001f3ff\u200d\u2642\ufe0f",
[":merperson:"] = "\U0001f9dc",
[":merperson_tone1:"] = "\U0001f9dc\U0001f3fb",
[":merperson_light_skin_tone:"] = "\U0001f9dc\U0001f3fb",
[":merperson::skin-tone-1:"] = "\U0001f9dc\U0001f3fb",
[":merperson_tone2:"] = "\U0001f9dc\U0001f3fc",
[":merperson_medium_light_skin_tone:"] = "\U0001f9dc\U0001f3fc",
[":merperson::skin-tone-2:"] = "\U0001f9dc\U0001f3fc",
[":merperson_tone3:"] = "\U0001f9dc\U0001f3fd",
[":merperson_medium_skin_tone:"] = "\U0001f9dc\U0001f3fd",
[":merperson::skin-tone-3:"] = "\U0001f9dc\U0001f3fd",
[":merperson_tone4:"] = "\U0001f9dc\U0001f3fe",
[":merperson_medium_dark_skin_tone:"] = "\U0001f9dc\U0001f3fe",
[":merperson::skin-tone-4:"] = "\U0001f9dc\U0001f3fe",
[":merperson_tone5:"] = "\U0001f9dc\U0001f3ff",
[":merperson_dark_skin_tone:"] = "\U0001f9dc\U0001f3ff",
[":merperson::skin-tone-5:"] = "\U0001f9dc\U0001f3ff",
[":metal:"] = "\U0001f918",
[":sign_of_the_horns:"] = "\U0001f918",
[":metal_tone1:"] = "\U0001f918\U0001f3fb",
[":sign_of_the_horns_tone1:"] = "\U0001f918\U0001f3fb",
[":metal::skin-tone-1:"] = "\U0001f918\U0001f3fb",
[":sign_of_the_horns::skin-tone-1:"] = "\U0001f918\U0001f3fb",
[":metal_tone2:"] = "\U0001f918\U0001f3fc",
[":sign_of_the_horns_tone2:"] = "\U0001f918\U0001f3fc",
[":metal::skin-tone-2:"] = "\U0001f918\U0001f3fc",
[":sign_of_the_horns::skin-tone-2:"] = "\U0001f918\U0001f3fc",
[":metal_tone3:"] = "\U0001f918\U0001f3fd",
[":sign_of_the_horns_tone3:"] = "\U0001f918\U0001f3fd",
[":metal::skin-tone-3:"] = "\U0001f918\U0001f3fd",
[":sign_of_the_horns::skin-tone-3:"] = "\U0001f918\U0001f3fd",
[":metal_tone4:"] = "\U0001f918\U0001f3fe",
[":sign_of_the_horns_tone4:"] = "\U0001f918\U0001f3fe",
[":metal::skin-tone-4:"] = "\U0001f918\U0001f3fe",
[":sign_of_the_horns::skin-tone-4:"] = "\U0001f918\U0001f3fe",
[":metal_tone5:"] = "\U0001f918\U0001f3ff",
[":sign_of_the_horns_tone5:"] = "\U0001f918\U0001f3ff",
[":metal::skin-tone-5:"] = "\U0001f918\U0001f3ff",
[":sign_of_the_horns::skin-tone-5:"] = "\U0001f918\U0001f3ff",
[":metro:"] = "\U0001f687",
[":microbe:"] = "\U0001f9a0",
[":microphone:"] = "\U0001f3a4",
[":microphone2:"] = "\U0001f399\ufe0f",
[":studio_microphone:"] = "\U0001f399\ufe0f",
[":microscope:"] = "\U0001f52c",
[":middle_finger:"] = "\U0001f595",
[":reversed_hand_with_middle_finger_extended:"] = "\U0001f595",
[":middle_finger_tone1:"] = "\U0001f595\U0001f3fb",
[":reversed_hand_with_middle_finger_extended_tone1:"] = "\U0001f595\U0001f3fb",
[":middle_finger::skin-tone-1:"] = "\U0001f595\U0001f3fb",
[":reversed_hand_with_middle_finger_extended::skin-tone-1:"] = "\U0001f595\U0001f3fb",
[":middle_finger_tone2:"] = "\U0001f595\U0001f3fc",
[":reversed_hand_with_middle_finger_extended_tone2:"] = "\U0001f595\U0001f3fc",
[":middle_finger::skin-tone-2:"] = "\U0001f595\U0001f3fc",
[":reversed_hand_with_middle_finger_extended::skin-tone-2:"] = "\U0001f595\U0001f3fc",
[":middle_finger_tone3:"] = "\U0001f595\U0001f3fd",
[":reversed_hand_with_middle_finger_extended_tone3:"] = "\U0001f595\U0001f3fd",
[":middle_finger::skin-tone-3:"] = "\U0001f595\U0001f3fd",
[":reversed_hand_with_middle_finger_extended::skin-tone-3:"] = "\U0001f595\U0001f3fd",
[":middle_finger_tone4:"] = "\U0001f595\U0001f3fe",
[":reversed_hand_with_middle_finger_extended_tone4:"] = "\U0001f595\U0001f3fe",
[":middle_finger::skin-tone-4:"] = "\U0001f595\U0001f3fe",
[":reversed_hand_with_middle_finger_extended::skin-tone-4:"] = "\U0001f595\U0001f3fe",
[":middle_finger_tone5:"] = "\U0001f595\U0001f3ff",
[":reversed_hand_with_middle_finger_extended_tone5:"] = "\U0001f595\U0001f3ff",
[":middle_finger::skin-tone-5:"] = "\U0001f595\U0001f3ff",
[":reversed_hand_with_middle_finger_extended::skin-tone-5:"] = "\U0001f595\U0001f3ff",
[":military_helmet:"] = "\U0001fa96",
[":military_medal:"] = "\U0001f396\ufe0f",
[":milk:"] = "\U0001f95b",
[":glass_of_milk:"] = "\U0001f95b",
[":milky_way:"] = "\U0001f30c",
[":minibus:"] = "\U0001f690",
[":minidisc:"] = "\U0001f4bd",
[":mirror:"] = "\U0001fa9e",
[":mobile_phone:"] = "\U0001f4f1",
[":iphone:"] = "\U0001f4f1",
[":mobile_phone_off:"] = "\U0001f4f4",
[":money_mouth:"] = "\U0001f911",
[":money_mouth_face:"] = "\U0001f911",
[":money_with_wings:"] = "\U0001f4b8",
[":moneybag:"] = "\U0001f4b0",
[":monkey:"] = "\U0001f412",
[":monkey_face:"] = "\U0001f435",
[":monorail:"] = "\U0001f69d",
[":moon_cake:"] = "\U0001f96e",
[":mortar_board:"] = "\U0001f393",
[":mosque:"] = "\U0001f54c",
[":mosquito:"] = "\U0001f99f",
[":motor_scooter:"] = "\U0001f6f5",
[":motorbike:"] = "\U0001f6f5",
[":motorboat:"] = "\U0001f6e5\ufe0f",
[":motorcycle:"] = "\U0001f3cd\ufe0f",
[":racing_motorcycle:"] = "\U0001f3cd\ufe0f",
[":motorized_wheelchair:"] = "\U0001f9bc",
[":motorway:"] = "\U0001f6e3\ufe0f",
[":mount_fuji:"] = "\U0001f5fb",
[":mountain:"] = "\u26f0\ufe0f",
[":mountain_cableway:"] = "\U0001f6a0",
[":mountain_railway:"] = "\U0001f69e",
[":mountain_snow:"] = "\U0001f3d4\ufe0f",
[":snow_capped_mountain:"] = "\U0001f3d4\ufe0f",
[":mouse:"] = "\U0001f42d",
[":mouse_three_button:"] = "\U0001f5b1\ufe0f",
[":three_button_mouse:"] = "\U0001f5b1\ufe0f",
[":mouse_trap:"] = "\U0001faa4",
[":mouse2:"] = "\U0001f401",
[":movie_camera:"] = "\U0001f3a5",
[":moyai:"] = "\U0001f5ff",
[":mrs_claus:"] = "\U0001f936",
[":mother_christmas:"] = "\U0001f936",
[":mrs_claus_tone1:"] = "\U0001f936\U0001f3fb",
[":mother_christmas_tone1:"] = "\U0001f936\U0001f3fb",
[":mrs_claus::skin-tone-1:"] = "\U0001f936\U0001f3fb",
[":mother_christmas::skin-tone-1:"] = "\U0001f936\U0001f3fb",
[":mrs_claus_tone2:"] = "\U0001f936\U0001f3fc",
[":mother_christmas_tone2:"] = "\U0001f936\U0001f3fc",
[":mrs_claus::skin-tone-2:"] = "\U0001f936\U0001f3fc",
[":mother_christmas::skin-tone-2:"] = "\U0001f936\U0001f3fc",
[":mrs_claus_tone3:"] = "\U0001f936\U0001f3fd",
[":mother_christmas_tone3:"] = "\U0001f936\U0001f3fd",
[":mrs_claus::skin-tone-3:"] = "\U0001f936\U0001f3fd",
[":mother_christmas::skin-tone-3:"] = "\U0001f936\U0001f3fd",
[":mrs_claus_tone4:"] = "\U0001f936\U0001f3fe",
[":mother_christmas_tone4:"] = "\U0001f936\U0001f3fe",
[":mrs_claus::skin-tone-4:"] = "\U0001f936\U0001f3fe",
[":mother_christmas::skin-tone-4:"] = "\U0001f936\U0001f3fe",
[":mrs_claus_tone5:"] = "\U0001f936\U0001f3ff",
[":mother_christmas_tone5:"] = "\U0001f936\U0001f3ff",
[":mrs_claus::skin-tone-5:"] = "\U0001f936\U0001f3ff",
[":mother_christmas::skin-tone-5:"] = "\U0001f936\U0001f3ff",
[":muscle:"] = "\U0001f4aa",
[":muscle_tone1:"] = "\U0001f4aa\U0001f3fb",
[":muscle::skin-tone-1:"] = "\U0001f4aa\U0001f3fb",
[":muscle_tone2:"] = "\U0001f4aa\U0001f3fc",
[":muscle::skin-tone-2:"] = "\U0001f4aa\U0001f3fc",
[":muscle_tone3:"] = "\U0001f4aa\U0001f3fd",
[":muscle::skin-tone-3:"] = "\U0001f4aa\U0001f3fd",
[":muscle_tone4:"] = "\U0001f4aa\U0001f3fe",
[":muscle::skin-tone-4:"] = "\U0001f4aa\U0001f3fe",
[":muscle_tone5:"] = "\U0001f4aa\U0001f3ff",
[":muscle::skin-tone-5:"] = "\U0001f4aa\U0001f3ff",
[":mushroom:"] = "\U0001f344",
[":musical_keyboard:"] = "\U0001f3b9",
[":musical_note:"] = "\U0001f3b5",
[":musical_score:"] = "\U0001f3bc",
[":mute:"] = "\U0001f507",
[":mx_claus:"] = "\U0001f9d1\u200d\U0001f384",
[":mx_claus_tone1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f384",
[":mx_claus_light_skin_tone:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f384",
[":mx_claus::skin-tone-1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f384",
[":mx_claus_tone2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f384",
[":mx_claus_medium_light_skin_tone:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f384",
[":mx_claus::skin-tone-2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f384",
[":mx_claus_tone3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f384",
[":mx_claus_medium_skin_tone:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f384",
[":mx_claus::skin-tone-3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f384",
[":mx_claus_tone4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f384",
[":mx_claus_medium_dark_skin_tone:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f384",
[":mx_claus::skin-tone-4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f384",
[":mx_claus_tone5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f384",
[":mx_claus_dark_skin_tone:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f384",
[":mx_claus::skin-tone-5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f384",
[":nail_care:"] = "\U0001f485",
[":nail_care_tone1:"] = "\U0001f485\U0001f3fb",
[":nail_care::skin-tone-1:"] = "\U0001f485\U0001f3fb",
[":nail_care_tone2:"] = "\U0001f485\U0001f3fc",
[":nail_care::skin-tone-2:"] = "\U0001f485\U0001f3fc",
[":nail_care_tone3:"] = "\U0001f485\U0001f3fd",
[":nail_care::skin-tone-3:"] = "\U0001f485\U0001f3fd",
[":nail_care_tone4:"] = "\U0001f485\U0001f3fe",
[":nail_care::skin-tone-4:"] = "\U0001f485\U0001f3fe",
[":nail_care_tone5:"] = "\U0001f485\U0001f3ff",
[":nail_care::skin-tone-5:"] = "\U0001f485\U0001f3ff",
[":name_badge:"] = "\U0001f4db",
[":nauseated_face:"] = "\U0001f922",
[":sick:"] = "\U0001f922",
[":nazar_amulet:"] = "\U0001f9ff",
[":necktie:"] = "\U0001f454",
[":negative_squared_cross_mark:"] = "\u274e",
[":nerd:"] = "\U0001f913",
[":nerd_face:"] = "\U0001f913",
[":nesting_dolls:"] = "\U0001fa86",
[":neutral_face:"] = "\U0001f610",
[":|"] = "\U0001f610",
[":-|"] = "\U0001f610",
["=|"] = "\U0001f610",
["=-|"] = "\U0001f610",
[":new:"] = "\U0001f195",
[":new_moon:"] = "\U0001f311",
[":new_moon_with_face:"] = "\U0001f31a",
[":newspaper:"] = "\U0001f4f0",
[":newspaper2:"] = "\U0001f5de\ufe0f",
[":rolled_up_newspaper:"] = "\U0001f5de\ufe0f",
[":ng:"] = "\U0001f196",
[":night_with_stars:"] = "\U0001f303",
[":nine:"] = "\u0039\ufe0f\u20e3",
[":ninja:"] = "\U0001f977",
[":ninja_tone1:"] = "\U0001f977\U0001f3fb",
[":ninja_light_skin_tone:"] = "\U0001f977\U0001f3fb",
[":ninja::skin-tone-1:"] = "\U0001f977\U0001f3fb",
[":ninja_tone2:"] = "\U0001f977\U0001f3fc",
[":ninja_medium_light_skin_tone:"] = "\U0001f977\U0001f3fc",
[":ninja::skin-tone-2:"] = "\U0001f977\U0001f3fc",
[":ninja_tone3:"] = "\U0001f977\U0001f3fd",
[":ninja_medium_skin_tone:"] = "\U0001f977\U0001f3fd",
[":ninja::skin-tone-3:"] = "\U0001f977\U0001f3fd",
[":ninja_tone4:"] = "\U0001f977\U0001f3fe",
[":ninja_medium_dark_skin_tone:"] = "\U0001f977\U0001f3fe",
[":ninja::skin-tone-4:"] = "\U0001f977\U0001f3fe",
[":ninja_tone5:"] = "\U0001f977\U0001f3ff",
[":ninja_dark_skin_tone:"] = "\U0001f977\U0001f3ff",
[":ninja::skin-tone-5:"] = "\U0001f977\U0001f3ff",
[":no_bell:"] = "\U0001f515",
[":no_bicycles:"] = "\U0001f6b3",
[":no_entry:"] = "\u26d4",
[":no_entry_sign:"] = "\U0001f6ab",
[":no_mobile_phones:"] = "\U0001f4f5",
[":no_mouth:"] = "\U0001f636",
[":no_pedestrians:"] = "\U0001f6b7",
[":no_smoking:"] = "\U0001f6ad",
[":non_potable_water:"] = "\U0001f6b1",
[":nose:"] = "\U0001f443",
[":nose_tone1:"] = "\U0001f443\U0001f3fb",
[":nose::skin-tone-1:"] = "\U0001f443\U0001f3fb",
[":nose_tone2:"] = "\U0001f443\U0001f3fc",
[":nose::skin-tone-2:"] = "\U0001f443\U0001f3fc",
[":nose_tone3:"] = "\U0001f443\U0001f3fd",
[":nose::skin-tone-3:"] = "\U0001f443\U0001f3fd",
[":nose_tone4:"] = "\U0001f443\U0001f3fe",
[":nose::skin-tone-4:"] = "\U0001f443\U0001f3fe",
[":nose_tone5:"] = "\U0001f443\U0001f3ff",
[":nose::skin-tone-5:"] = "\U0001f443\U0001f3ff",
[":notebook:"] = "\U0001f4d3",
[":notebook_with_decorative_cover:"] = "\U0001f4d4",
[":notepad_spiral:"] = "\U0001f5d2\ufe0f",
[":spiral_note_pad:"] = "\U0001f5d2\ufe0f",
[":notes:"] = "\U0001f3b6",
[":nut_and_bolt:"] = "\U0001f529",
[":o:"] = "\u2b55",
[":o2:"] = "\U0001f17e\ufe0f",
[":ocean:"] = "\U0001f30a",
[":octagonal_sign:"] = "\U0001f6d1",
[":stop_sign:"] = "\U0001f6d1",
[":octopus:"] = "\U0001f419",
[":oden:"] = "\U0001f362",
[":office:"] = "\U0001f3e2",
[":office_worker:"] = "\U0001f9d1\u200d\U0001f4bc",
[":office_worker_tone1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f4bc",
[":office_worker_light_skin_tone:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f4bc",
[":office_worker::skin-tone-1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f4bc",
[":office_worker_tone2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f4bc",
[":office_worker_medium_light_skin_tone:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f4bc",
[":office_worker::skin-tone-2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f4bc",
[":office_worker_tone3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f4bc",
[":office_worker_medium_skin_tone:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f4bc",
[":office_worker::skin-tone-3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f4bc",
[":office_worker_tone4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f4bc",
[":office_worker_medium_dark_skin_tone:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f4bc",
[":office_worker::skin-tone-4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f4bc",
[":office_worker_tone5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f4bc",
[":office_worker_dark_skin_tone:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f4bc",
[":office_worker::skin-tone-5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f4bc",
[":oil:"] = "\U0001f6e2\ufe0f",
[":oil_drum:"] = "\U0001f6e2\ufe0f",
[":ok:"] = "\U0001f197",
[":ok_hand:"] = "\U0001f44c",
[":ok_hand_tone1:"] = "\U0001f44c\U0001f3fb",
[":ok_hand::skin-tone-1:"] = "\U0001f44c\U0001f3fb",
[":ok_hand_tone2:"] = "\U0001f44c\U0001f3fc",
[":ok_hand::skin-tone-2:"] = "\U0001f44c\U0001f3fc",
[":ok_hand_tone3:"] = "\U0001f44c\U0001f3fd",
[":ok_hand::skin-tone-3:"] = "\U0001f44c\U0001f3fd",
[":ok_hand_tone4:"] = "\U0001f44c\U0001f3fe",
[":ok_hand::skin-tone-4:"] = "\U0001f44c\U0001f3fe",
[":ok_hand_tone5:"] = "\U0001f44c\U0001f3ff",
[":ok_hand::skin-tone-5:"] = "\U0001f44c\U0001f3ff",
[":older_adult:"] = "\U0001f9d3",
[":older_adult_tone1:"] = "\U0001f9d3\U0001f3fb",
[":older_adult_light_skin_tone:"] = "\U0001f9d3\U0001f3fb",
[":older_adult::skin-tone-1:"] = "\U0001f9d3\U0001f3fb",
[":older_adult_tone2:"] = "\U0001f9d3\U0001f3fc",
[":older_adult_medium_light_skin_tone:"] = "\U0001f9d3\U0001f3fc",
[":older_adult::skin-tone-2:"] = "\U0001f9d3\U0001f3fc",
[":older_adult_tone3:"] = "\U0001f9d3\U0001f3fd",
[":older_adult_medium_skin_tone:"] = "\U0001f9d3\U0001f3fd",
[":older_adult::skin-tone-3:"] = "\U0001f9d3\U0001f3fd",
[":older_adult_tone4:"] = "\U0001f9d3\U0001f3fe",
[":older_adult_medium_dark_skin_tone:"] = "\U0001f9d3\U0001f3fe",
[":older_adult::skin-tone-4:"] = "\U0001f9d3\U0001f3fe",
[":older_adult_tone5:"] = "\U0001f9d3\U0001f3ff",
[":older_adult_dark_skin_tone:"] = "\U0001f9d3\U0001f3ff",
[":older_adult::skin-tone-5:"] = "\U0001f9d3\U0001f3ff",
[":older_man:"] = "\U0001f474",
[":older_man_tone1:"] = "\U0001f474\U0001f3fb",
[":older_man::skin-tone-1:"] = "\U0001f474\U0001f3fb",
[":older_man_tone2:"] = "\U0001f474\U0001f3fc",
[":older_man::skin-tone-2:"] = "\U0001f474\U0001f3fc",
[":older_man_tone3:"] = "\U0001f474\U0001f3fd",
[":older_man::skin-tone-3:"] = "\U0001f474\U0001f3fd",
[":older_man_tone4:"] = "\U0001f474\U0001f3fe",
[":older_man::skin-tone-4:"] = "\U0001f474\U0001f3fe",
[":older_man_tone5:"] = "\U0001f474\U0001f3ff",
[":older_man::skin-tone-5:"] = "\U0001f474\U0001f3ff",
[":older_woman:"] = "\U0001f475",
[":grandma:"] = "\U0001f475",
[":older_woman_tone1:"] = "\U0001f475\U0001f3fb",
[":grandma_tone1:"] = "\U0001f475\U0001f3fb",
[":older_woman::skin-tone-1:"] = "\U0001f475\U0001f3fb",
[":grandma::skin-tone-1:"] = "\U0001f475\U0001f3fb",
[":older_woman_tone2:"] = "\U0001f475\U0001f3fc",
[":grandma_tone2:"] = "\U0001f475\U0001f3fc",
[":older_woman::skin-tone-2:"] = "\U0001f475\U0001f3fc",
[":grandma::skin-tone-2:"] = "\U0001f475\U0001f3fc",
[":older_woman_tone3:"] = "\U0001f475\U0001f3fd",
[":grandma_tone3:"] = "\U0001f475\U0001f3fd",
[":older_woman::skin-tone-3:"] = "\U0001f475\U0001f3fd",
[":grandma::skin-tone-3:"] = "\U0001f475\U0001f3fd",
[":older_woman_tone4:"] = "\U0001f475\U0001f3fe",
[":grandma_tone4:"] = "\U0001f475\U0001f3fe",
[":older_woman::skin-tone-4:"] = "\U0001f475\U0001f3fe",
[":grandma::skin-tone-4:"] = "\U0001f475\U0001f3fe",
[":older_woman_tone5:"] = "\U0001f475\U0001f3ff",
[":grandma_tone5:"] = "\U0001f475\U0001f3ff",
[":older_woman::skin-tone-5:"] = "\U0001f475\U0001f3ff",
[":grandma::skin-tone-5:"] = "\U0001f475\U0001f3ff",
[":olive:"] = "\U0001fad2",
[":om_symbol:"] = "\U0001f549\ufe0f",
[":on:"] = "\U0001f51b",
[":oncoming_automobile:"] = "\U0001f698",
[":oncoming_bus:"] = "\U0001f68d",
[":oncoming_police_car:"] = "\U0001f694",
[":oncoming_taxi:"] = "\U0001f696",
[":one:"] = "\u0031\ufe0f\u20e3",
[":one_piece_swimsuit:"] = "\U0001fa71",
[":onion:"] = "\U0001f9c5",
[":open_file_folder:"] = "\U0001f4c2",
[":open_hands:"] = "\U0001f450",
[":open_hands_tone1:"] = "\U0001f450\U0001f3fb",
[":open_hands::skin-tone-1:"] = "\U0001f450\U0001f3fb",
[":open_hands_tone2:"] = "\U0001f450\U0001f3fc",
[":open_hands::skin-tone-2:"] = "\U0001f450\U0001f3fc",
[":open_hands_tone3:"] = "\U0001f450\U0001f3fd",
[":open_hands::skin-tone-3:"] = "\U0001f450\U0001f3fd",
[":open_hands_tone4:"] = "\U0001f450\U0001f3fe",
[":open_hands::skin-tone-4:"] = "\U0001f450\U0001f3fe",
[":open_hands_tone5:"] = "\U0001f450\U0001f3ff",
[":open_hands::skin-tone-5:"] = "\U0001f450\U0001f3ff",
[":open_mouth:"] = "\U0001f62e",
[":o"] = "\U0001f62e",
[":-o"] = "\U0001f62e",
[":O"] = "\U0001f62e",
[":-O"] = "\U0001f62e",
["=o"] = "\U0001f62e",
["=-o"] = "\U0001f62e",
["=O"] = "\U0001f62e",
["=-O"] = "\U0001f62e",
[":ophiuchus:"] = "\u26ce",
[":orange_book:"] = "\U0001f4d9",
[":orange_circle:"] = "\U0001f7e0",
[":orange_heart:"] = "\U0001f9e1",
[":orange_square:"] = "\U0001f7e7",
[":orangutan:"] = "\U0001f9a7",
[":orthodox_cross:"] = "\u2626\ufe0f",
[":otter:"] = "\U0001f9a6",
[":outbox_tray:"] = "\U0001f4e4",
[":owl:"] = "\U0001f989",
[":ox:"] = "\U0001f402",
[":oyster:"] = "\U0001f9aa",
[":package:"] = "\U0001f4e6",
[":page_facing_up:"] = "\U0001f4c4",
[":page_with_curl:"] = "\U0001f4c3",
[":pager:"] = "\U0001f4df",
[":paintbrush:"] = "\U0001f58c\ufe0f",
[":lower_left_paintbrush:"] = "\U0001f58c\ufe0f",
[":palm_tree:"] = "\U0001f334",
[":palms_up_together:"] = "\U0001f932",
[":palms_up_together_tone1:"] = "\U0001f932\U0001f3fb",
[":palms_up_together_light_skin_tone:"] = "\U0001f932\U0001f3fb",
[":palms_up_together::skin-tone-1:"] = "\U0001f932\U0001f3fb",
[":palms_up_together_tone2:"] = "\U0001f932\U0001f3fc",
[":palms_up_together_medium_light_skin_tone:"] = "\U0001f932\U0001f3fc",
[":palms_up_together::skin-tone-2:"] = "\U0001f932\U0001f3fc",
[":palms_up_together_tone3:"] = "\U0001f932\U0001f3fd",
[":palms_up_together_medium_skin_tone:"] = "\U0001f932\U0001f3fd",
[":palms_up_together::skin-tone-3:"] = "\U0001f932\U0001f3fd",
[":palms_up_together_tone4:"] = "\U0001f932\U0001f3fe",
[":palms_up_together_medium_dark_skin_tone:"] = "\U0001f932\U0001f3fe",
[":palms_up_together::skin-tone-4:"] = "\U0001f932\U0001f3fe",
[":palms_up_together_tone5:"] = "\U0001f932\U0001f3ff",
[":palms_up_together_dark_skin_tone:"] = "\U0001f932\U0001f3ff",
[":palms_up_together::skin-tone-5:"] = "\U0001f932\U0001f3ff",
[":pancakes:"] = "\U0001f95e",
[":panda_face:"] = "\U0001f43c",
[":paperclip:"] = "\U0001f4ce",
[":paperclips:"] = "\U0001f587\ufe0f",
[":linked_paperclips:"] = "\U0001f587\ufe0f",
[":parachute:"] = "\U0001fa82",
[":park:"] = "\U0001f3de\ufe0f",
[":national_park:"] = "\U0001f3de\ufe0f",
[":parking:"] = "\U0001f17f\ufe0f",
[":parrot:"] = "\U0001f99c",
[":part_alternation_mark:"] = "\u303d\ufe0f",
[":partly_sunny:"] = "\u26c5",
[":partying_face:"] = "\U0001f973",
[":passport_control:"] = "\U0001f6c2",
[":pause_button:"] = "\u23f8\ufe0f",
[":double_vertical_bar:"] = "\u23f8\ufe0f",
[":peace:"] = "\u262e\ufe0f",
[":peace_symbol:"] = "\u262e\ufe0f",
[":peach:"] = "\U0001f351",
[":peacock:"] = "\U0001f99a",
[":peanuts:"] = "\U0001f95c",
[":shelled_peanut:"] = "\U0001f95c",
[":pear:"] = "\U0001f350",
[":pen_ballpoint:"] = "\U0001f58a\ufe0f",
[":lower_left_ballpoint_pen:"] = "\U0001f58a\ufe0f",
[":pen_fountain:"] = "\U0001f58b\ufe0f",
[":lower_left_fountain_pen:"] = "\U0001f58b\ufe0f",
[":pencil:"] = "\U0001f4dd",
[":memo:"] = "\U0001f4dd",
[":pencil2:"] = "\u270f\ufe0f",
[":penguin:"] = "\U0001f427",
[":pensive:"] = "\U0001f614",
[":people_holding_hands:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_tone1:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_light_skin_tone:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_tone1_tone2:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_light_skin_tone_medium_light_skin_tone:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_tone1_tone3:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_light_skin_tone_medium_skin_tone:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_tone1_tone4:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_light_skin_tone_medium_dark_skin_tone:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_tone1_tone5:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_light_skin_tone_dark_skin_tone:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_tone2:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_medium_light_skin_tone:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_tone2_tone1:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_medium_light_skin_tone_light_skin_tone:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_tone2_tone3:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_medium_light_skin_tone_medium_skin_tone:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_tone2_tone4:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_medium_light_skin_tone_medium_dark_skin_tone:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_tone2_tone5:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_medium_light_skin_tone_dark_skin_tone:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_tone3:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_medium_skin_tone:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_tone3_tone1:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_medium_skin_tone_light_skin_tone:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_tone3_tone2:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_medium_skin_tone_medium_light_skin_tone:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_tone3_tone4:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_medium_skin_tone_medium_dark_skin_tone:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_tone3_tone5:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_medium_skin_tone_dark_skin_tone:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_tone4:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_medium_dark_skin_tone:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_tone4_tone1:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_medium_dark_skin_tone_light_skin_tone:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_tone4_tone2:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_medium_dark_skin_tone_medium_light_skin_tone:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_tone4_tone3:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_medium_dark_skin_tone_medium_skin_tone:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_tone4_tone5:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_medium_dark_skin_tone_dark_skin_tone:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_tone5:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_dark_skin_tone:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_tone5_tone1:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_dark_skin_tone_light_skin_tone:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_tone5_tone2:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_dark_skin_tone_medium_light_skin_tone:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_tone5_tone3:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_dark_skin_tone_medium_skin_tone:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_tone5_tone4:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_holding_hands_dark_skin_tone_medium_dark_skin_tone:"] = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1",
[":people_hugging:"] = "\U0001fac2",
[":people_with_bunny_ears_partying:"] = "\U0001f46f",
[":dancers:"] = "\U0001f46f",
[":people_wrestling:"] = "\U0001f93c",
[":wrestlers:"] = "\U0001f93c",
[":wrestling:"] = "\U0001f93c",
[":performing_arts:"] = "\U0001f3ad",
[":persevere:"] = "\U0001f623",
[":person_bald:"] = "\U0001f9d1\u200d\U0001f9b2",
[":person_biking:"] = "\U0001f6b4",
[":bicyclist:"] = "\U0001f6b4",
[":person_biking_tone1:"] = "\U0001f6b4\U0001f3fb",
[":bicyclist_tone1:"] = "\U0001f6b4\U0001f3fb",
[":person_biking::skin-tone-1:"] = "\U0001f6b4\U0001f3fb",
[":bicyclist::skin-tone-1:"] = "\U0001f6b4\U0001f3fb",
[":person_biking_tone2:"] = "\U0001f6b4\U0001f3fc",
[":bicyclist_tone2:"] = "\U0001f6b4\U0001f3fc",
[":person_biking::skin-tone-2:"] = "\U0001f6b4\U0001f3fc",
[":bicyclist::skin-tone-2:"] = "\U0001f6b4\U0001f3fc",
[":person_biking_tone3:"] = "\U0001f6b4\U0001f3fd",
[":bicyclist_tone3:"] = "\U0001f6b4\U0001f3fd",
[":person_biking::skin-tone-3:"] = "\U0001f6b4\U0001f3fd",
[":bicyclist::skin-tone-3:"] = "\U0001f6b4\U0001f3fd",
[":person_biking_tone4:"] = "\U0001f6b4\U0001f3fe",
[":bicyclist_tone4:"] = "\U0001f6b4\U0001f3fe",
[":person_biking::skin-tone-4:"] = "\U0001f6b4\U0001f3fe",
[":bicyclist::skin-tone-4:"] = "\U0001f6b4\U0001f3fe",
[":person_biking_tone5:"] = "\U0001f6b4\U0001f3ff",
[":bicyclist_tone5:"] = "\U0001f6b4\U0001f3ff",
[":person_biking::skin-tone-5:"] = "\U0001f6b4\U0001f3ff",
[":bicyclist::skin-tone-5:"] = "\U0001f6b4\U0001f3ff",
[":person_bouncing_ball:"] = "\u26f9\ufe0f",
[":basketball_player:"] = "\u26f9\ufe0f",
[":person_with_ball:"] = "\u26f9\ufe0f",
[":person_bouncing_ball_tone1:"] = "\u26f9\U0001f3fb",
[":basketball_player_tone1:"] = "\u26f9\U0001f3fb",
[":person_with_ball_tone1:"] = "\u26f9\U0001f3fb",
[":person_bouncing_ball::skin-tone-1:"] = "\u26f9\U0001f3fb",
[":basketball_player::skin-tone-1:"] = "\u26f9\U0001f3fb",
[":person_with_ball::skin-tone-1:"] = "\u26f9\U0001f3fb",
[":person_bouncing_ball_tone2:"] = "\u26f9\U0001f3fc",
[":basketball_player_tone2:"] = "\u26f9\U0001f3fc",
[":person_with_ball_tone2:"] = "\u26f9\U0001f3fc",
[":person_bouncing_ball::skin-tone-2:"] = "\u26f9\U0001f3fc",
[":basketball_player::skin-tone-2:"] = "\u26f9\U0001f3fc",
[":person_with_ball::skin-tone-2:"] = "\u26f9\U0001f3fc",
[":person_bouncing_ball_tone3:"] = "\u26f9\U0001f3fd",
[":basketball_player_tone3:"] = "\u26f9\U0001f3fd",
[":person_with_ball_tone3:"] = "\u26f9\U0001f3fd",
[":person_bouncing_ball::skin-tone-3:"] = "\u26f9\U0001f3fd",
[":basketball_player::skin-tone-3:"] = "\u26f9\U0001f3fd",
[":person_with_ball::skin-tone-3:"] = "\u26f9\U0001f3fd",
[":person_bouncing_ball_tone4:"] = "\u26f9\U0001f3fe",
[":basketball_player_tone4:"] = "\u26f9\U0001f3fe",
[":person_with_ball_tone4:"] = "\u26f9\U0001f3fe",
[":person_bouncing_ball::skin-tone-4:"] = "\u26f9\U0001f3fe",
[":basketball_player::skin-tone-4:"] = "\u26f9\U0001f3fe",
[":person_with_ball::skin-tone-4:"] = "\u26f9\U0001f3fe",
[":person_bouncing_ball_tone5:"] = "\u26f9\U0001f3ff",
[":basketball_player_tone5:"] = "\u26f9\U0001f3ff",
[":person_with_ball_tone5:"] = "\u26f9\U0001f3ff",
[":person_bouncing_ball::skin-tone-5:"] = "\u26f9\U0001f3ff",
[":basketball_player::skin-tone-5:"] = "\u26f9\U0001f3ff",
[":person_with_ball::skin-tone-5:"] = "\u26f9\U0001f3ff",
[":person_bowing:"] = "\U0001f647",
[":bow:"] = "\U0001f647",
[":person_bowing_tone1:"] = "\U0001f647\U0001f3fb",
[":bow_tone1:"] = "\U0001f647\U0001f3fb",
[":person_bowing::skin-tone-1:"] = "\U0001f647\U0001f3fb",
[":bow::skin-tone-1:"] = "\U0001f647\U0001f3fb",
[":person_bowing_tone2:"] = "\U0001f647\U0001f3fc",
[":bow_tone2:"] = "\U0001f647\U0001f3fc",
[":person_bowing::skin-tone-2:"] = "\U0001f647\U0001f3fc",
[":bow::skin-tone-2:"] = "\U0001f647\U0001f3fc",
[":person_bowing_tone3:"] = "\U0001f647\U0001f3fd",
[":bow_tone3:"] = "\U0001f647\U0001f3fd",
[":person_bowing::skin-tone-3:"] = "\U0001f647\U0001f3fd",
[":bow::skin-tone-3:"] = "\U0001f647\U0001f3fd",
[":person_bowing_tone4:"] = "\U0001f647\U0001f3fe",
[":bow_tone4:"] = "\U0001f647\U0001f3fe",
[":person_bowing::skin-tone-4:"] = "\U0001f647\U0001f3fe",
[":bow::skin-tone-4:"] = "\U0001f647\U0001f3fe",
[":person_bowing_tone5:"] = "\U0001f647\U0001f3ff",
[":bow_tone5:"] = "\U0001f647\U0001f3ff",
[":person_bowing::skin-tone-5:"] = "\U0001f647\U0001f3ff",
[":bow::skin-tone-5:"] = "\U0001f647\U0001f3ff",
[":person_climbing:"] = "\U0001f9d7",
[":person_climbing_tone1:"] = "\U0001f9d7\U0001f3fb",
[":person_climbing_light_skin_tone:"] = "\U0001f9d7\U0001f3fb",
[":person_climbing::skin-tone-1:"] = "\U0001f9d7\U0001f3fb",
[":person_climbing_tone2:"] = "\U0001f9d7\U0001f3fc",
[":person_climbing_medium_light_skin_tone:"] = "\U0001f9d7\U0001f3fc",
[":person_climbing::skin-tone-2:"] = "\U0001f9d7\U0001f3fc",
[":person_climbing_tone3:"] = "\U0001f9d7\U0001f3fd",
[":person_climbing_medium_skin_tone:"] = "\U0001f9d7\U0001f3fd",
[":person_climbing::skin-tone-3:"] = "\U0001f9d7\U0001f3fd",
[":person_climbing_tone4:"] = "\U0001f9d7\U0001f3fe",
[":person_climbing_medium_dark_skin_tone:"] = "\U0001f9d7\U0001f3fe",
[":person_climbing::skin-tone-4:"] = "\U0001f9d7\U0001f3fe",
[":person_climbing_tone5:"] = "\U0001f9d7\U0001f3ff",
[":person_climbing_dark_skin_tone:"] = "\U0001f9d7\U0001f3ff",
[":person_climbing::skin-tone-5:"] = "\U0001f9d7\U0001f3ff",
[":person_curly_hair:"] = "\U0001f9d1\u200d\U0001f9b1",
[":person_doing_cartwheel:"] = "\U0001f938",
[":cartwheel:"] = "\U0001f938",
[":person_doing_cartwheel_tone1:"] = "\U0001f938\U0001f3fb",
[":cartwheel_tone1:"] = "\U0001f938\U0001f3fb",
[":person_doing_cartwheel::skin-tone-1:"] = "\U0001f938\U0001f3fb",
[":cartwheel::skin-tone-1:"] = "\U0001f938\U0001f3fb",
[":person_doing_cartwheel_tone2:"] = "\U0001f938\U0001f3fc",
[":cartwheel_tone2:"] = "\U0001f938\U0001f3fc",
[":person_doing_cartwheel::skin-tone-2:"] = "\U0001f938\U0001f3fc",
[":cartwheel::skin-tone-2:"] = "\U0001f938\U0001f3fc",
[":person_doing_cartwheel_tone3:"] = "\U0001f938\U0001f3fd",
[":cartwheel_tone3:"] = "\U0001f938\U0001f3fd",
[":person_doing_cartwheel::skin-tone-3:"] = "\U0001f938\U0001f3fd",
[":cartwheel::skin-tone-3:"] = "\U0001f938\U0001f3fd",
[":person_doing_cartwheel_tone4:"] = "\U0001f938\U0001f3fe",
[":cartwheel_tone4:"] = "\U0001f938\U0001f3fe",
[":person_doing_cartwheel::skin-tone-4:"] = "\U0001f938\U0001f3fe",
[":cartwheel::skin-tone-4:"] = "\U0001f938\U0001f3fe",
[":person_doing_cartwheel_tone5:"] = "\U0001f938\U0001f3ff",
[":cartwheel_tone5:"] = "\U0001f938\U0001f3ff",
[":person_doing_cartwheel::skin-tone-5:"] = "\U0001f938\U0001f3ff",
[":cartwheel::skin-tone-5:"] = "\U0001f938\U0001f3ff",
[":person_facepalming:"] = "\U0001f926",
[":face_palm:"] = "\U0001f926",
[":facepalm:"] = "\U0001f926",
[":person_facepalming_tone1:"] = "\U0001f926\U0001f3fb",
[":face_palm_tone1:"] = "\U0001f926\U0001f3fb",
[":facepalm_tone1:"] = "\U0001f926\U0001f3fb",
[":person_facepalming::skin-tone-1:"] = "\U0001f926\U0001f3fb",
[":face_palm::skin-tone-1:"] = "\U0001f926\U0001f3fb",
[":facepalm::skin-tone-1:"] = "\U0001f926\U0001f3fb",
[":person_facepalming_tone2:"] = "\U0001f926\U0001f3fc",
[":face_palm_tone2:"] = "\U0001f926\U0001f3fc",
[":facepalm_tone2:"] = "\U0001f926\U0001f3fc",
[":person_facepalming::skin-tone-2:"] = "\U0001f926\U0001f3fc",
[":face_palm::skin-tone-2:"] = "\U0001f926\U0001f3fc",
[":facepalm::skin-tone-2:"] = "\U0001f926\U0001f3fc",
[":person_facepalming_tone3:"] = "\U0001f926\U0001f3fd",
[":face_palm_tone3:"] = "\U0001f926\U0001f3fd",
[":facepalm_tone3:"] = "\U0001f926\U0001f3fd",
[":person_facepalming::skin-tone-3:"] = "\U0001f926\U0001f3fd",
[":face_palm::skin-tone-3:"] = "\U0001f926\U0001f3fd",
[":facepalm::skin-tone-3:"] = "\U0001f926\U0001f3fd",
[":person_facepalming_tone4:"] = "\U0001f926\U0001f3fe",
[":face_palm_tone4:"] = "\U0001f926\U0001f3fe",
[":facepalm_tone4:"] = "\U0001f926\U0001f3fe",
[":person_facepalming::skin-tone-4:"] = "\U0001f926\U0001f3fe",
[":face_palm::skin-tone-4:"] = "\U0001f926\U0001f3fe",
[":facepalm::skin-tone-4:"] = "\U0001f926\U0001f3fe",
[":person_facepalming_tone5:"] = "\U0001f926\U0001f3ff",
[":face_palm_tone5:"] = "\U0001f926\U0001f3ff",
[":facepalm_tone5:"] = "\U0001f926\U0001f3ff",
[":person_facepalming::skin-tone-5:"] = "\U0001f926\U0001f3ff",
[":face_palm::skin-tone-5:"] = "\U0001f926\U0001f3ff",
[":facepalm::skin-tone-5:"] = "\U0001f926\U0001f3ff",
[":person_feeding_baby:"] = "\U0001f9d1\u200d\U0001f37c",
[":person_feeding_baby_tone1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f37c",
[":person_feeding_baby_light_skin_tone:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f37c",
[":person_feeding_baby::skin-tone-1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f37c",
[":person_feeding_baby_tone2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f37c",
[":person_feeding_baby_medium_light_skin_tone:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f37c",
[":person_feeding_baby::skin-tone-2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f37c",
[":person_feeding_baby_tone3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f37c",
[":person_feeding_baby_medium_skin_tone:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f37c",
[":person_feeding_baby::skin-tone-3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f37c",
[":person_feeding_baby_tone4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f37c",
[":person_feeding_baby_medium_dark_skin_tone:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f37c",
[":person_feeding_baby::skin-tone-4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f37c",
[":person_feeding_baby_tone5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f37c",
[":person_feeding_baby_dark_skin_tone:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f37c",
[":person_feeding_baby::skin-tone-5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f37c",
[":person_fencing:"] = "\U0001f93a",
[":fencer:"] = "\U0001f93a",
[":fencing:"] = "\U0001f93a",
[":person_frowning:"] = "\U0001f64d",
[":person_frowning_tone1:"] = "\U0001f64d\U0001f3fb",
[":person_frowning::skin-tone-1:"] = "\U0001f64d\U0001f3fb",
[":person_frowning_tone2:"] = "\U0001f64d\U0001f3fc",
[":person_frowning::skin-tone-2:"] = "\U0001f64d\U0001f3fc",
[":person_frowning_tone3:"] = "\U0001f64d\U0001f3fd",
[":person_frowning::skin-tone-3:"] = "\U0001f64d\U0001f3fd",
[":person_frowning_tone4:"] = "\U0001f64d\U0001f3fe",
[":person_frowning::skin-tone-4:"] = "\U0001f64d\U0001f3fe",
[":person_frowning_tone5:"] = "\U0001f64d\U0001f3ff",
[":person_frowning::skin-tone-5:"] = "\U0001f64d\U0001f3ff",
[":person_gesturing_no:"] = "\U0001f645",
[":no_good:"] = "\U0001f645",
[":person_gesturing_no_tone1:"] = "\U0001f645\U0001f3fb",
[":no_good_tone1:"] = "\U0001f645\U0001f3fb",
[":person_gesturing_no::skin-tone-1:"] = "\U0001f645\U0001f3fb",
[":no_good::skin-tone-1:"] = "\U0001f645\U0001f3fb",
[":person_gesturing_no_tone2:"] = "\U0001f645\U0001f3fc",
[":no_good_tone2:"] = "\U0001f645\U0001f3fc",
[":person_gesturing_no::skin-tone-2:"] = "\U0001f645\U0001f3fc",
[":no_good::skin-tone-2:"] = "\U0001f645\U0001f3fc",
[":person_gesturing_no_tone3:"] = "\U0001f645\U0001f3fd",
[":no_good_tone3:"] = "\U0001f645\U0001f3fd",
[":person_gesturing_no::skin-tone-3:"] = "\U0001f645\U0001f3fd",
[":no_good::skin-tone-3:"] = "\U0001f645\U0001f3fd",
[":person_gesturing_no_tone4:"] = "\U0001f645\U0001f3fe",
[":no_good_tone4:"] = "\U0001f645\U0001f3fe",
[":person_gesturing_no::skin-tone-4:"] = "\U0001f645\U0001f3fe",
[":no_good::skin-tone-4:"] = "\U0001f645\U0001f3fe",
[":person_gesturing_no_tone5:"] = "\U0001f645\U0001f3ff",
[":no_good_tone5:"] = "\U0001f645\U0001f3ff",
[":person_gesturing_no::skin-tone-5:"] = "\U0001f645\U0001f3ff",
[":no_good::skin-tone-5:"] = "\U0001f645\U0001f3ff",
[":person_gesturing_ok:"] = "\U0001f646",
[":ok_woman:"] = "\U0001f646",
[":person_gesturing_ok_tone1:"] = "\U0001f646\U0001f3fb",
[":ok_woman_tone1:"] = "\U0001f646\U0001f3fb",
[":person_gesturing_ok::skin-tone-1:"] = "\U0001f646\U0001f3fb",
[":ok_woman::skin-tone-1:"] = "\U0001f646\U0001f3fb",
[":person_gesturing_ok_tone2:"] = "\U0001f646\U0001f3fc",
[":ok_woman_tone2:"] = "\U0001f646\U0001f3fc",
[":person_gesturing_ok::skin-tone-2:"] = "\U0001f646\U0001f3fc",
[":ok_woman::skin-tone-2:"] = "\U0001f646\U0001f3fc",
[":person_gesturing_ok_tone3:"] = "\U0001f646\U0001f3fd",
[":ok_woman_tone3:"] = "\U0001f646\U0001f3fd",
[":person_gesturing_ok::skin-tone-3:"] = "\U0001f646\U0001f3fd",
[":ok_woman::skin-tone-3:"] = "\U0001f646\U0001f3fd",
[":person_gesturing_ok_tone4:"] = "\U0001f646\U0001f3fe",
[":ok_woman_tone4:"] = "\U0001f646\U0001f3fe",
[":person_gesturing_ok::skin-tone-4:"] = "\U0001f646\U0001f3fe",
[":ok_woman::skin-tone-4:"] = "\U0001f646\U0001f3fe",
[":person_gesturing_ok_tone5:"] = "\U0001f646\U0001f3ff",
[":ok_woman_tone5:"] = "\U0001f646\U0001f3ff",
[":person_gesturing_ok::skin-tone-5:"] = "\U0001f646\U0001f3ff",
[":ok_woman::skin-tone-5:"] = "\U0001f646\U0001f3ff",
[":person_getting_haircut:"] = "\U0001f487",
[":haircut:"] = "\U0001f487",
[":person_getting_haircut_tone1:"] = "\U0001f487\U0001f3fb",
[":haircut_tone1:"] = "\U0001f487\U0001f3fb",
[":person_getting_haircut::skin-tone-1:"] = "\U0001f487\U0001f3fb",
[":haircut::skin-tone-1:"] = "\U0001f487\U0001f3fb",
[":person_getting_haircut_tone2:"] = "\U0001f487\U0001f3fc",
[":haircut_tone2:"] = "\U0001f487\U0001f3fc",
[":person_getting_haircut::skin-tone-2:"] = "\U0001f487\U0001f3fc",
[":haircut::skin-tone-2:"] = "\U0001f487\U0001f3fc",
[":person_getting_haircut_tone3:"] = "\U0001f487\U0001f3fd",
[":haircut_tone3:"] = "\U0001f487\U0001f3fd",
[":person_getting_haircut::skin-tone-3:"] = "\U0001f487\U0001f3fd",
[":haircut::skin-tone-3:"] = "\U0001f487\U0001f3fd",
[":person_getting_haircut_tone4:"] = "\U0001f487\U0001f3fe",
[":haircut_tone4:"] = "\U0001f487\U0001f3fe",
[":person_getting_haircut::skin-tone-4:"] = "\U0001f487\U0001f3fe",
[":haircut::skin-tone-4:"] = "\U0001f487\U0001f3fe",
[":person_getting_haircut_tone5:"] = "\U0001f487\U0001f3ff",
[":haircut_tone5:"] = "\U0001f487\U0001f3ff",
[":person_getting_haircut::skin-tone-5:"] = "\U0001f487\U0001f3ff",
[":haircut::skin-tone-5:"] = "\U0001f487\U0001f3ff",
[":person_getting_massage:"] = "\U0001f486",
[":massage:"] = "\U0001f486",
[":person_getting_massage_tone1:"] = "\U0001f486\U0001f3fb",
[":massage_tone1:"] = "\U0001f486\U0001f3fb",
[":person_getting_massage::skin-tone-1:"] = "\U0001f486\U0001f3fb",
[":massage::skin-tone-1:"] = "\U0001f486\U0001f3fb",
[":person_getting_massage_tone2:"] = "\U0001f486\U0001f3fc",
[":massage_tone2:"] = "\U0001f486\U0001f3fc",
[":person_getting_massage::skin-tone-2:"] = "\U0001f486\U0001f3fc",
[":massage::skin-tone-2:"] = "\U0001f486\U0001f3fc",
[":person_getting_massage_tone3:"] = "\U0001f486\U0001f3fd",
[":massage_tone3:"] = "\U0001f486\U0001f3fd",
[":person_getting_massage::skin-tone-3:"] = "\U0001f486\U0001f3fd",
[":massage::skin-tone-3:"] = "\U0001f486\U0001f3fd",
[":person_getting_massage_tone4:"] = "\U0001f486\U0001f3fe",
[":massage_tone4:"] = "\U0001f486\U0001f3fe",
[":person_getting_massage::skin-tone-4:"] = "\U0001f486\U0001f3fe",
[":massage::skin-tone-4:"] = "\U0001f486\U0001f3fe",
[":person_getting_massage_tone5:"] = "\U0001f486\U0001f3ff",
[":massage_tone5:"] = "\U0001f486\U0001f3ff",
[":person_getting_massage::skin-tone-5:"] = "\U0001f486\U0001f3ff",
[":massage::skin-tone-5:"] = "\U0001f486\U0001f3ff",
[":person_golfing:"] = "\U0001f3cc\ufe0f",
[":golfer:"] = "\U0001f3cc\ufe0f",
[":person_golfing_tone1:"] = "\U0001f3cc\U0001f3fb",
[":person_golfing_light_skin_tone:"] = "\U0001f3cc\U0001f3fb",
[":person_golfing::skin-tone-1:"] = "\U0001f3cc\U0001f3fb",
[":golfer::skin-tone-1:"] = "\U0001f3cc\U0001f3fb",
[":person_golfing_tone2:"] = "\U0001f3cc\U0001f3fc",
[":person_golfing_medium_light_skin_tone:"] = "\U0001f3cc\U0001f3fc",
[":person_golfing::skin-tone-2:"] = "\U0001f3cc\U0001f3fc",
[":golfer::skin-tone-2:"] = "\U0001f3cc\U0001f3fc",
[":person_golfing_tone3:"] = "\U0001f3cc\U0001f3fd",
[":person_golfing_medium_skin_tone:"] = "\U0001f3cc\U0001f3fd",
[":person_golfing::skin-tone-3:"] = "\U0001f3cc\U0001f3fd",
[":golfer::skin-tone-3:"] = "\U0001f3cc\U0001f3fd",
[":person_golfing_tone4:"] = "\U0001f3cc\U0001f3fe",
[":person_golfing_medium_dark_skin_tone:"] = "\U0001f3cc\U0001f3fe",
[":person_golfing::skin-tone-4:"] = "\U0001f3cc\U0001f3fe",
[":golfer::skin-tone-4:"] = "\U0001f3cc\U0001f3fe",
[":person_golfing_tone5:"] = "\U0001f3cc\U0001f3ff",
[":person_golfing_dark_skin_tone:"] = "\U0001f3cc\U0001f3ff",
[":person_golfing::skin-tone-5:"] = "\U0001f3cc\U0001f3ff",
[":golfer::skin-tone-5:"] = "\U0001f3cc\U0001f3ff",
[":person_in_bed_tone1:"] = "\U0001f6cc\U0001f3fb",
[":person_in_bed_light_skin_tone:"] = "\U0001f6cc\U0001f3fb",
[":sleeping_accommodation::skin-tone-1:"] = "\U0001f6cc\U0001f3fb",
[":person_in_bed_tone2:"] = "\U0001f6cc\U0001f3fc",
[":person_in_bed_medium_light_skin_tone:"] = "\U0001f6cc\U0001f3fc",
[":sleeping_accommodation::skin-tone-2:"] = "\U0001f6cc\U0001f3fc",
[":person_in_bed_tone3:"] = "\U0001f6cc\U0001f3fd",
[":person_in_bed_medium_skin_tone:"] = "\U0001f6cc\U0001f3fd",
[":sleeping_accommodation::skin-tone-3:"] = "\U0001f6cc\U0001f3fd",
[":person_in_bed_tone4:"] = "\U0001f6cc\U0001f3fe",
[":person_in_bed_medium_dark_skin_tone:"] = "\U0001f6cc\U0001f3fe",
[":sleeping_accommodation::skin-tone-4:"] = "\U0001f6cc\U0001f3fe",
[":person_in_bed_tone5:"] = "\U0001f6cc\U0001f3ff",
[":person_in_bed_dark_skin_tone:"] = "\U0001f6cc\U0001f3ff",
[":sleeping_accommodation::skin-tone-5:"] = "\U0001f6cc\U0001f3ff",
[":person_in_lotus_position:"] = "\U0001f9d8",
[":person_in_lotus_position_tone1:"] = "\U0001f9d8\U0001f3fb",
[":person_in_lotus_position_light_skin_tone:"] = "\U0001f9d8\U0001f3fb",
[":person_in_lotus_position::skin-tone-1:"] = "\U0001f9d8\U0001f3fb",
[":person_in_lotus_position_tone2:"] = "\U0001f9d8\U0001f3fc",
[":person_in_lotus_position_medium_light_skin_tone:"] = "\U0001f9d8\U0001f3fc",
[":person_in_lotus_position::skin-tone-2:"] = "\U0001f9d8\U0001f3fc",
[":person_in_lotus_position_tone3:"] = "\U0001f9d8\U0001f3fd",
[":person_in_lotus_position_medium_skin_tone:"] = "\U0001f9d8\U0001f3fd",
[":person_in_lotus_position::skin-tone-3:"] = "\U0001f9d8\U0001f3fd",
[":person_in_lotus_position_tone4:"] = "\U0001f9d8\U0001f3fe",
[":person_in_lotus_position_medium_dark_skin_tone:"] = "\U0001f9d8\U0001f3fe",
[":person_in_lotus_position::skin-tone-4:"] = "\U0001f9d8\U0001f3fe",
[":person_in_lotus_position_tone5:"] = "\U0001f9d8\U0001f3ff",
[":person_in_lotus_position_dark_skin_tone:"] = "\U0001f9d8\U0001f3ff",
[":person_in_lotus_position::skin-tone-5:"] = "\U0001f9d8\U0001f3ff",
[":person_in_manual_wheelchair:"] = "\U0001f9d1\u200d\U0001f9bd",
[":person_in_manual_wheelchair_tone1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f9bd",
[":person_in_manual_wheelchair_light_skin_tone:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f9bd",
[":person_in_manual_wheelchair::skin-tone-1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f9bd",
[":person_in_manual_wheelchair_tone2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f9bd",
[":person_in_manual_wheelchair_medium_light_skin_tone:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f9bd",
[":person_in_manual_wheelchair::skin-tone-2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f9bd",
[":person_in_manual_wheelchair_tone3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f9bd",
[":person_in_manual_wheelchair_medium_skin_tone:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f9bd",
[":person_in_manual_wheelchair::skin-tone-3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f9bd",
[":person_in_manual_wheelchair_tone4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f9bd",
[":person_in_manual_wheelchair_medium_dark_skin_tone:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f9bd",
[":person_in_manual_wheelchair::skin-tone-4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f9bd",
[":person_in_manual_wheelchair_tone5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f9bd",
[":person_in_manual_wheelchair_dark_skin_tone:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f9bd",
[":person_in_manual_wheelchair::skin-tone-5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f9bd",
[":person_in_motorized_wheelchair:"] = "\U0001f9d1\u200d\U0001f9bc",
[":person_in_motorized_wheelchair_tone1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f9bc",
[":person_in_motorized_wheelchair_light_skin_tone:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f9bc",
[":person_in_motorized_wheelchair::skin-tone-1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f9bc",
[":person_in_motorized_wheelchair_tone2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f9bc",
[":person_in_motorized_wheelchair_medium_light_skin_tone:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f9bc",
[":person_in_motorized_wheelchair::skin-tone-2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f9bc",
[":person_in_motorized_wheelchair_tone3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f9bc",
[":person_in_motorized_wheelchair_medium_skin_tone:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f9bc",
[":person_in_motorized_wheelchair::skin-tone-3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f9bc",
[":person_in_motorized_wheelchair_tone4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f9bc",
[":person_in_motorized_wheelchair_medium_dark_skin_tone:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f9bc",
[":person_in_motorized_wheelchair::skin-tone-4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f9bc",
[":person_in_motorized_wheelchair_tone5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f9bc",
[":person_in_motorized_wheelchair_dark_skin_tone:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f9bc",
[":person_in_motorized_wheelchair::skin-tone-5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f9bc",
[":person_in_steamy_room:"] = "\U0001f9d6",
[":person_in_steamy_room_tone1:"] = "\U0001f9d6\U0001f3fb",
[":person_in_steamy_room_light_skin_tone:"] = "\U0001f9d6\U0001f3fb",
[":person_in_steamy_room::skin-tone-1:"] = "\U0001f9d6\U0001f3fb",
[":person_in_steamy_room_tone2:"] = "\U0001f9d6\U0001f3fc",
[":person_in_steamy_room_medium_light_skin_tone:"] = "\U0001f9d6\U0001f3fc",
[":person_in_steamy_room::skin-tone-2:"] = "\U0001f9d6\U0001f3fc",
[":person_in_steamy_room_tone3:"] = "\U0001f9d6\U0001f3fd",
[":person_in_steamy_room_medium_skin_tone:"] = "\U0001f9d6\U0001f3fd",
[":person_in_steamy_room::skin-tone-3:"] = "\U0001f9d6\U0001f3fd",
[":person_in_steamy_room_tone4:"] = "\U0001f9d6\U0001f3fe",
[":person_in_steamy_room_medium_dark_skin_tone:"] = "\U0001f9d6\U0001f3fe",
[":person_in_steamy_room::skin-tone-4:"] = "\U0001f9d6\U0001f3fe",
[":person_in_steamy_room_tone5:"] = "\U0001f9d6\U0001f3ff",
[":person_in_steamy_room_dark_skin_tone:"] = "\U0001f9d6\U0001f3ff",
[":person_in_steamy_room::skin-tone-5:"] = "\U0001f9d6\U0001f3ff",
[":person_in_tuxedo:"] = "\U0001f935",
[":person_in_tuxedo_tone1:"] = "\U0001f935\U0001f3fb",
[":tuxedo_tone1:"] = "\U0001f935\U0001f3fb",
[":person_in_tuxedo::skin-tone-1:"] = "\U0001f935\U0001f3fb",
[":person_in_tuxedo_tone2:"] = "\U0001f935\U0001f3fc",
[":tuxedo_tone2:"] = "\U0001f935\U0001f3fc",
[":person_in_tuxedo::skin-tone-2:"] = "\U0001f935\U0001f3fc",
[":person_in_tuxedo_tone3:"] = "\U0001f935\U0001f3fd",
[":tuxedo_tone3:"] = "\U0001f935\U0001f3fd",
[":person_in_tuxedo::skin-tone-3:"] = "\U0001f935\U0001f3fd",
[":person_in_tuxedo_tone4:"] = "\U0001f935\U0001f3fe",
[":tuxedo_tone4:"] = "\U0001f935\U0001f3fe",
[":person_in_tuxedo::skin-tone-4:"] = "\U0001f935\U0001f3fe",
[":person_in_tuxedo_tone5:"] = "\U0001f935\U0001f3ff",
[":tuxedo_tone5:"] = "\U0001f935\U0001f3ff",
[":person_in_tuxedo::skin-tone-5:"] = "\U0001f935\U0001f3ff",
[":person_juggling:"] = "\U0001f939",
[":juggling:"] = "\U0001f939",
[":juggler:"] = "\U0001f939",
[":person_juggling_tone1:"] = "\U0001f939\U0001f3fb",
[":juggling_tone1:"] = "\U0001f939\U0001f3fb",
[":juggler_tone1:"] = "\U0001f939\U0001f3fb",
[":person_juggling::skin-tone-1:"] = "\U0001f939\U0001f3fb",
[":juggling::skin-tone-1:"] = "\U0001f939\U0001f3fb",
[":juggler::skin-tone-1:"] = "\U0001f939\U0001f3fb",
[":person_juggling_tone2:"] = "\U0001f939\U0001f3fc",
[":juggling_tone2:"] = "\U0001f939\U0001f3fc",
[":juggler_tone2:"] = "\U0001f939\U0001f3fc",
[":person_juggling::skin-tone-2:"] = "\U0001f939\U0001f3fc",
[":juggling::skin-tone-2:"] = "\U0001f939\U0001f3fc",
[":juggler::skin-tone-2:"] = "\U0001f939\U0001f3fc",
[":person_juggling_tone3:"] = "\U0001f939\U0001f3fd",
[":juggling_tone3:"] = "\U0001f939\U0001f3fd",
[":juggler_tone3:"] = "\U0001f939\U0001f3fd",
[":person_juggling::skin-tone-3:"] = "\U0001f939\U0001f3fd",
[":juggling::skin-tone-3:"] = "\U0001f939\U0001f3fd",
[":juggler::skin-tone-3:"] = "\U0001f939\U0001f3fd",
[":person_juggling_tone4:"] = "\U0001f939\U0001f3fe",
[":juggling_tone4:"] = "\U0001f939\U0001f3fe",
[":juggler_tone4:"] = "\U0001f939\U0001f3fe",
[":person_juggling::skin-tone-4:"] = "\U0001f939\U0001f3fe",
[":juggling::skin-tone-4:"] = "\U0001f939\U0001f3fe",
[":juggler::skin-tone-4:"] = "\U0001f939\U0001f3fe",
[":person_juggling_tone5:"] = "\U0001f939\U0001f3ff",
[":juggling_tone5:"] = "\U0001f939\U0001f3ff",
[":juggler_tone5:"] = "\U0001f939\U0001f3ff",
[":person_juggling::skin-tone-5:"] = "\U0001f939\U0001f3ff",
[":juggling::skin-tone-5:"] = "\U0001f939\U0001f3ff",
[":juggler::skin-tone-5:"] = "\U0001f939\U0001f3ff",
[":person_kneeling:"] = "\U0001f9ce",
[":person_kneeling_tone1:"] = "\U0001f9ce\U0001f3fb",
[":person_kneeling_light_skin_tone:"] = "\U0001f9ce\U0001f3fb",
[":person_kneeling::skin-tone-1:"] = "\U0001f9ce\U0001f3fb",
[":person_kneeling_tone2:"] = "\U0001f9ce\U0001f3fc",
[":person_kneeling_medium_light_skin_tone:"] = "\U0001f9ce\U0001f3fc",
[":person_kneeling::skin-tone-2:"] = "\U0001f9ce\U0001f3fc",
[":person_kneeling_tone3:"] = "\U0001f9ce\U0001f3fd",
[":person_kneeling_medium_skin_tone:"] = "\U0001f9ce\U0001f3fd",
[":person_kneeling::skin-tone-3:"] = "\U0001f9ce\U0001f3fd",
[":person_kneeling_tone4:"] = "\U0001f9ce\U0001f3fe",
[":person_kneeling_medium_dark_skin_tone:"] = "\U0001f9ce\U0001f3fe",
[":person_kneeling::skin-tone-4:"] = "\U0001f9ce\U0001f3fe",
[":person_kneeling_tone5:"] = "\U0001f9ce\U0001f3ff",
[":person_kneeling_dark_skin_tone:"] = "\U0001f9ce\U0001f3ff",
[":person_kneeling::skin-tone-5:"] = "\U0001f9ce\U0001f3ff",
[":person_lifting_weights:"] = "\U0001f3cb\ufe0f",
[":lifter:"] = "\U0001f3cb\ufe0f",
[":weight_lifter:"] = "\U0001f3cb\ufe0f",
[":person_lifting_weights_tone1:"] = "\U0001f3cb\U0001f3fb",
[":lifter_tone1:"] = "\U0001f3cb\U0001f3fb",
[":weight_lifter_tone1:"] = "\U0001f3cb\U0001f3fb",
[":person_lifting_weights::skin-tone-1:"] = "\U0001f3cb\U0001f3fb",
[":lifter::skin-tone-1:"] = "\U0001f3cb\U0001f3fb",
[":weight_lifter::skin-tone-1:"] = "\U0001f3cb\U0001f3fb",
[":person_lifting_weights_tone2:"] = "\U0001f3cb\U0001f3fc",
[":lifter_tone2:"] = "\U0001f3cb\U0001f3fc",
[":weight_lifter_tone2:"] = "\U0001f3cb\U0001f3fc",
[":person_lifting_weights::skin-tone-2:"] = "\U0001f3cb\U0001f3fc",
[":lifter::skin-tone-2:"] = "\U0001f3cb\U0001f3fc",
[":weight_lifter::skin-tone-2:"] = "\U0001f3cb\U0001f3fc",
[":person_lifting_weights_tone3:"] = "\U0001f3cb\U0001f3fd",
[":lifter_tone3:"] = "\U0001f3cb\U0001f3fd",
[":weight_lifter_tone3:"] = "\U0001f3cb\U0001f3fd",
[":person_lifting_weights::skin-tone-3:"] = "\U0001f3cb\U0001f3fd",
[":lifter::skin-tone-3:"] = "\U0001f3cb\U0001f3fd",
[":weight_lifter::skin-tone-3:"] = "\U0001f3cb\U0001f3fd",
[":person_lifting_weights_tone4:"] = "\U0001f3cb\U0001f3fe",
[":lifter_tone4:"] = "\U0001f3cb\U0001f3fe",
[":weight_lifter_tone4:"] = "\U0001f3cb\U0001f3fe",
[":person_lifting_weights::skin-tone-4:"] = "\U0001f3cb\U0001f3fe",
[":lifter::skin-tone-4:"] = "\U0001f3cb\U0001f3fe",
[":weight_lifter::skin-tone-4:"] = "\U0001f3cb\U0001f3fe",
[":person_lifting_weights_tone5:"] = "\U0001f3cb\U0001f3ff",
[":lifter_tone5:"] = "\U0001f3cb\U0001f3ff",
[":weight_lifter_tone5:"] = "\U0001f3cb\U0001f3ff",
[":person_lifting_weights::skin-tone-5:"] = "\U0001f3cb\U0001f3ff",
[":lifter::skin-tone-5:"] = "\U0001f3cb\U0001f3ff",
[":weight_lifter::skin-tone-5:"] = "\U0001f3cb\U0001f3ff",
[":person_mountain_biking:"] = "\U0001f6b5",
[":mountain_bicyclist:"] = "\U0001f6b5",
[":person_mountain_biking_tone1:"] = "\U0001f6b5\U0001f3fb",
[":mountain_bicyclist_tone1:"] = "\U0001f6b5\U0001f3fb",
[":person_mountain_biking::skin-tone-1:"] = "\U0001f6b5\U0001f3fb",
[":mountain_bicyclist::skin-tone-1:"] = "\U0001f6b5\U0001f3fb",
[":person_mountain_biking_tone2:"] = "\U0001f6b5\U0001f3fc",
[":mountain_bicyclist_tone2:"] = "\U0001f6b5\U0001f3fc",
[":person_mountain_biking::skin-tone-2:"] = "\U0001f6b5\U0001f3fc",
[":mountain_bicyclist::skin-tone-2:"] = "\U0001f6b5\U0001f3fc",
[":person_mountain_biking_tone3:"] = "\U0001f6b5\U0001f3fd",
[":mountain_bicyclist_tone3:"] = "\U0001f6b5\U0001f3fd",
[":person_mountain_biking::skin-tone-3:"] = "\U0001f6b5\U0001f3fd",
[":mountain_bicyclist::skin-tone-3:"] = "\U0001f6b5\U0001f3fd",
[":person_mountain_biking_tone4:"] = "\U0001f6b5\U0001f3fe",
[":mountain_bicyclist_tone4:"] = "\U0001f6b5\U0001f3fe",
[":person_mountain_biking::skin-tone-4:"] = "\U0001f6b5\U0001f3fe",
[":mountain_bicyclist::skin-tone-4:"] = "\U0001f6b5\U0001f3fe",
[":person_mountain_biking_tone5:"] = "\U0001f6b5\U0001f3ff",
[":mountain_bicyclist_tone5:"] = "\U0001f6b5\U0001f3ff",
[":person_mountain_biking::skin-tone-5:"] = "\U0001f6b5\U0001f3ff",
[":mountain_bicyclist::skin-tone-5:"] = "\U0001f6b5\U0001f3ff",
[":person_playing_handball:"] = "\U0001f93e",
[":handball:"] = "\U0001f93e",
[":person_playing_handball_tone1:"] = "\U0001f93e\U0001f3fb",
[":handball_tone1:"] = "\U0001f93e\U0001f3fb",
[":person_playing_handball::skin-tone-1:"] = "\U0001f93e\U0001f3fb",
[":handball::skin-tone-1:"] = "\U0001f93e\U0001f3fb",
[":person_playing_handball_tone2:"] = "\U0001f93e\U0001f3fc",
[":handball_tone2:"] = "\U0001f93e\U0001f3fc",
[":person_playing_handball::skin-tone-2:"] = "\U0001f93e\U0001f3fc",
[":handball::skin-tone-2:"] = "\U0001f93e\U0001f3fc",
[":person_playing_handball_tone3:"] = "\U0001f93e\U0001f3fd",
[":handball_tone3:"] = "\U0001f93e\U0001f3fd",
[":person_playing_handball::skin-tone-3:"] = "\U0001f93e\U0001f3fd",
[":handball::skin-tone-3:"] = "\U0001f93e\U0001f3fd",
[":person_playing_handball_tone4:"] = "\U0001f93e\U0001f3fe",
[":handball_tone4:"] = "\U0001f93e\U0001f3fe",
[":person_playing_handball::skin-tone-4:"] = "\U0001f93e\U0001f3fe",
[":handball::skin-tone-4:"] = "\U0001f93e\U0001f3fe",
[":person_playing_handball_tone5:"] = "\U0001f93e\U0001f3ff",
[":handball_tone5:"] = "\U0001f93e\U0001f3ff",
[":person_playing_handball::skin-tone-5:"] = "\U0001f93e\U0001f3ff",
[":handball::skin-tone-5:"] = "\U0001f93e\U0001f3ff",
[":person_playing_water_polo:"] = "\U0001f93d",
[":water_polo:"] = "\U0001f93d",
[":person_playing_water_polo_tone1:"] = "\U0001f93d\U0001f3fb",
[":water_polo_tone1:"] = "\U0001f93d\U0001f3fb",
[":person_playing_water_polo::skin-tone-1:"] = "\U0001f93d\U0001f3fb",
[":water_polo::skin-tone-1:"] = "\U0001f93d\U0001f3fb",
[":person_playing_water_polo_tone2:"] = "\U0001f93d\U0001f3fc",
[":water_polo_tone2:"] = "\U0001f93d\U0001f3fc",
[":person_playing_water_polo::skin-tone-2:"] = "\U0001f93d\U0001f3fc",
[":water_polo::skin-tone-2:"] = "\U0001f93d\U0001f3fc",
[":person_playing_water_polo_tone3:"] = "\U0001f93d\U0001f3fd",
[":water_polo_tone3:"] = "\U0001f93d\U0001f3fd",
[":person_playing_water_polo::skin-tone-3:"] = "\U0001f93d\U0001f3fd",
[":water_polo::skin-tone-3:"] = "\U0001f93d\U0001f3fd",
[":person_playing_water_polo_tone4:"] = "\U0001f93d\U0001f3fe",
[":water_polo_tone4:"] = "\U0001f93d\U0001f3fe",
[":person_playing_water_polo::skin-tone-4:"] = "\U0001f93d\U0001f3fe",
[":water_polo::skin-tone-4:"] = "\U0001f93d\U0001f3fe",
[":person_playing_water_polo_tone5:"] = "\U0001f93d\U0001f3ff",
[":water_polo_tone5:"] = "\U0001f93d\U0001f3ff",
[":person_playing_water_polo::skin-tone-5:"] = "\U0001f93d\U0001f3ff",
[":water_polo::skin-tone-5:"] = "\U0001f93d\U0001f3ff",
[":person_pouting:"] = "\U0001f64e",
[":person_with_pouting_face:"] = "\U0001f64e",
[":person_pouting_tone1:"] = "\U0001f64e\U0001f3fb",
[":person_with_pouting_face_tone1:"] = "\U0001f64e\U0001f3fb",
[":person_pouting::skin-tone-1:"] = "\U0001f64e\U0001f3fb",
[":person_with_pouting_face::skin-tone-1:"] = "\U0001f64e\U0001f3fb",
[":person_pouting_tone2:"] = "\U0001f64e\U0001f3fc",
[":person_with_pouting_face_tone2:"] = "\U0001f64e\U0001f3fc",
[":person_pouting::skin-tone-2:"] = "\U0001f64e\U0001f3fc",
[":person_with_pouting_face::skin-tone-2:"] = "\U0001f64e\U0001f3fc",
[":person_pouting_tone3:"] = "\U0001f64e\U0001f3fd",
[":person_with_pouting_face_tone3:"] = "\U0001f64e\U0001f3fd",
[":person_pouting::skin-tone-3:"] = "\U0001f64e\U0001f3fd",
[":person_with_pouting_face::skin-tone-3:"] = "\U0001f64e\U0001f3fd",
[":person_pouting_tone4:"] = "\U0001f64e\U0001f3fe",
[":person_with_pouting_face_tone4:"] = "\U0001f64e\U0001f3fe",
[":person_pouting::skin-tone-4:"] = "\U0001f64e\U0001f3fe",
[":person_with_pouting_face::skin-tone-4:"] = "\U0001f64e\U0001f3fe",
[":person_pouting_tone5:"] = "\U0001f64e\U0001f3ff",
[":person_with_pouting_face_tone5:"] = "\U0001f64e\U0001f3ff",
[":person_pouting::skin-tone-5:"] = "\U0001f64e\U0001f3ff",
[":person_with_pouting_face::skin-tone-5:"] = "\U0001f64e\U0001f3ff",
[":person_raising_hand:"] = "\U0001f64b",
[":raising_hand:"] = "\U0001f64b",
[":person_raising_hand_tone1:"] = "\U0001f64b\U0001f3fb",
[":raising_hand_tone1:"] = "\U0001f64b\U0001f3fb",
[":person_raising_hand::skin-tone-1:"] = "\U0001f64b\U0001f3fb",
[":raising_hand::skin-tone-1:"] = "\U0001f64b\U0001f3fb",
[":person_raising_hand_tone2:"] = "\U0001f64b\U0001f3fc",
[":raising_hand_tone2:"] = "\U0001f64b\U0001f3fc",
[":person_raising_hand::skin-tone-2:"] = "\U0001f64b\U0001f3fc",
[":raising_hand::skin-tone-2:"] = "\U0001f64b\U0001f3fc",
[":person_raising_hand_tone3:"] = "\U0001f64b\U0001f3fd",
[":raising_hand_tone3:"] = "\U0001f64b\U0001f3fd",
[":person_raising_hand::skin-tone-3:"] = "\U0001f64b\U0001f3fd",
[":raising_hand::skin-tone-3:"] = "\U0001f64b\U0001f3fd",
[":person_raising_hand_tone4:"] = "\U0001f64b\U0001f3fe",
[":raising_hand_tone4:"] = "\U0001f64b\U0001f3fe",
[":person_raising_hand::skin-tone-4:"] = "\U0001f64b\U0001f3fe",
[":raising_hand::skin-tone-4:"] = "\U0001f64b\U0001f3fe",
[":person_raising_hand_tone5:"] = "\U0001f64b\U0001f3ff",
[":raising_hand_tone5:"] = "\U0001f64b\U0001f3ff",
[":person_raising_hand::skin-tone-5:"] = "\U0001f64b\U0001f3ff",
[":raising_hand::skin-tone-5:"] = "\U0001f64b\U0001f3ff",
[":person_red_hair:"] = "\U0001f9d1\u200d\U0001f9b0",
[":person_rowing_boat:"] = "\U0001f6a3",
[":rowboat:"] = "\U0001f6a3",
[":person_rowing_boat_tone1:"] = "\U0001f6a3\U0001f3fb",
[":rowboat_tone1:"] = "\U0001f6a3\U0001f3fb",
[":person_rowing_boat::skin-tone-1:"] = "\U0001f6a3\U0001f3fb",
[":rowboat::skin-tone-1:"] = "\U0001f6a3\U0001f3fb",
[":person_rowing_boat_tone2:"] = "\U0001f6a3\U0001f3fc",
[":rowboat_tone2:"] = "\U0001f6a3\U0001f3fc",
[":person_rowing_boat::skin-tone-2:"] = "\U0001f6a3\U0001f3fc",
[":rowboat::skin-tone-2:"] = "\U0001f6a3\U0001f3fc",
[":person_rowing_boat_tone3:"] = "\U0001f6a3\U0001f3fd",
[":rowboat_tone3:"] = "\U0001f6a3\U0001f3fd",
[":person_rowing_boat::skin-tone-3:"] = "\U0001f6a3\U0001f3fd",
[":rowboat::skin-tone-3:"] = "\U0001f6a3\U0001f3fd",
[":person_rowing_boat_tone4:"] = "\U0001f6a3\U0001f3fe",
[":rowboat_tone4:"] = "\U0001f6a3\U0001f3fe",
[":person_rowing_boat::skin-tone-4:"] = "\U0001f6a3\U0001f3fe",
[":rowboat::skin-tone-4:"] = "\U0001f6a3\U0001f3fe",
[":person_rowing_boat_tone5:"] = "\U0001f6a3\U0001f3ff",
[":rowboat_tone5:"] = "\U0001f6a3\U0001f3ff",
[":person_rowing_boat::skin-tone-5:"] = "\U0001f6a3\U0001f3ff",
[":rowboat::skin-tone-5:"] = "\U0001f6a3\U0001f3ff",
[":person_running:"] = "\U0001f3c3",
[":runner:"] = "\U0001f3c3",
[":person_running_tone1:"] = "\U0001f3c3\U0001f3fb",
[":runner_tone1:"] = "\U0001f3c3\U0001f3fb",
[":person_running::skin-tone-1:"] = "\U0001f3c3\U0001f3fb",
[":runner::skin-tone-1:"] = "\U0001f3c3\U0001f3fb",
[":person_running_tone2:"] = "\U0001f3c3\U0001f3fc",
[":runner_tone2:"] = "\U0001f3c3\U0001f3fc",
[":person_running::skin-tone-2:"] = "\U0001f3c3\U0001f3fc",
[":runner::skin-tone-2:"] = "\U0001f3c3\U0001f3fc",
[":person_running_tone3:"] = "\U0001f3c3\U0001f3fd",
[":runner_tone3:"] = "\U0001f3c3\U0001f3fd",
[":person_running::skin-tone-3:"] = "\U0001f3c3\U0001f3fd",
[":runner::skin-tone-3:"] = "\U0001f3c3\U0001f3fd",
[":person_running_tone4:"] = "\U0001f3c3\U0001f3fe",
[":runner_tone4:"] = "\U0001f3c3\U0001f3fe",
[":person_running::skin-tone-4:"] = "\U0001f3c3\U0001f3fe",
[":runner::skin-tone-4:"] = "\U0001f3c3\U0001f3fe",
[":person_running_tone5:"] = "\U0001f3c3\U0001f3ff",
[":runner_tone5:"] = "\U0001f3c3\U0001f3ff",
[":person_running::skin-tone-5:"] = "\U0001f3c3\U0001f3ff",
[":runner::skin-tone-5:"] = "\U0001f3c3\U0001f3ff",
[":person_shrugging:"] = "\U0001f937",
[":shrug:"] = "\U0001f937",
[":person_shrugging_tone1:"] = "\U0001f937\U0001f3fb",
[":shrug_tone1:"] = "\U0001f937\U0001f3fb",
[":person_shrugging::skin-tone-1:"] = "\U0001f937\U0001f3fb",
[":shrug::skin-tone-1:"] = "\U0001f937\U0001f3fb",
[":person_shrugging_tone2:"] = "\U0001f937\U0001f3fc",
[":shrug_tone2:"] = "\U0001f937\U0001f3fc",
[":person_shrugging::skin-tone-2:"] = "\U0001f937\U0001f3fc",
[":shrug::skin-tone-2:"] = "\U0001f937\U0001f3fc",
[":person_shrugging_tone3:"] = "\U0001f937\U0001f3fd",
[":shrug_tone3:"] = "\U0001f937\U0001f3fd",
[":person_shrugging::skin-tone-3:"] = "\U0001f937\U0001f3fd",
[":shrug::skin-tone-3:"] = "\U0001f937\U0001f3fd",
[":person_shrugging_tone4:"] = "\U0001f937\U0001f3fe",
[":shrug_tone4:"] = "\U0001f937\U0001f3fe",
[":person_shrugging::skin-tone-4:"] = "\U0001f937\U0001f3fe",
[":shrug::skin-tone-4:"] = "\U0001f937\U0001f3fe",
[":person_shrugging_tone5:"] = "\U0001f937\U0001f3ff",
[":shrug_tone5:"] = "\U0001f937\U0001f3ff",
[":person_shrugging::skin-tone-5:"] = "\U0001f937\U0001f3ff",
[":shrug::skin-tone-5:"] = "\U0001f937\U0001f3ff",
[":person_standing:"] = "\U0001f9cd",
[":person_standing_tone1:"] = "\U0001f9cd\U0001f3fb",
[":person_standing_light_skin_tone:"] = "\U0001f9cd\U0001f3fb",
[":person_standing::skin-tone-1:"] = "\U0001f9cd\U0001f3fb",
[":person_standing_tone2:"] = "\U0001f9cd\U0001f3fc",
[":person_standing_medium_light_skin_tone:"] = "\U0001f9cd\U0001f3fc",
[":person_standing::skin-tone-2:"] = "\U0001f9cd\U0001f3fc",
[":person_standing_tone3:"] = "\U0001f9cd\U0001f3fd",
[":person_standing_medium_skin_tone:"] = "\U0001f9cd\U0001f3fd",
[":person_standing::skin-tone-3:"] = "\U0001f9cd\U0001f3fd",
[":person_standing_tone4:"] = "\U0001f9cd\U0001f3fe",
[":person_standing_medium_dark_skin_tone:"] = "\U0001f9cd\U0001f3fe",
[":person_standing::skin-tone-4:"] = "\U0001f9cd\U0001f3fe",
[":person_standing_tone5:"] = "\U0001f9cd\U0001f3ff",
[":person_standing_dark_skin_tone:"] = "\U0001f9cd\U0001f3ff",
[":person_standing::skin-tone-5:"] = "\U0001f9cd\U0001f3ff",
[":person_surfing:"] = "\U0001f3c4",
[":surfer:"] = "\U0001f3c4",
[":person_surfing_tone1:"] = "\U0001f3c4\U0001f3fb",
[":surfer_tone1:"] = "\U0001f3c4\U0001f3fb",
[":person_surfing::skin-tone-1:"] = "\U0001f3c4\U0001f3fb",
[":surfer::skin-tone-1:"] = "\U0001f3c4\U0001f3fb",
[":person_surfing_tone2:"] = "\U0001f3c4\U0001f3fc",
[":surfer_tone2:"] = "\U0001f3c4\U0001f3fc",
[":person_surfing::skin-tone-2:"] = "\U0001f3c4\U0001f3fc",
[":surfer::skin-tone-2:"] = "\U0001f3c4\U0001f3fc",
[":person_surfing_tone3:"] = "\U0001f3c4\U0001f3fd",
[":surfer_tone3:"] = "\U0001f3c4\U0001f3fd",
[":person_surfing::skin-tone-3:"] = "\U0001f3c4\U0001f3fd",
[":surfer::skin-tone-3:"] = "\U0001f3c4\U0001f3fd",
[":person_surfing_tone4:"] = "\U0001f3c4\U0001f3fe",
[":surfer_tone4:"] = "\U0001f3c4\U0001f3fe",
[":person_surfing::skin-tone-4:"] = "\U0001f3c4\U0001f3fe",
[":surfer::skin-tone-4:"] = "\U0001f3c4\U0001f3fe",
[":person_surfing_tone5:"] = "\U0001f3c4\U0001f3ff",
[":surfer_tone5:"] = "\U0001f3c4\U0001f3ff",
[":person_surfing::skin-tone-5:"] = "\U0001f3c4\U0001f3ff",
[":surfer::skin-tone-5:"] = "\U0001f3c4\U0001f3ff",
[":person_swimming:"] = "\U0001f3ca",
[":swimmer:"] = "\U0001f3ca",
[":person_swimming_tone1:"] = "\U0001f3ca\U0001f3fb",
[":swimmer_tone1:"] = "\U0001f3ca\U0001f3fb",
[":person_swimming::skin-tone-1:"] = "\U0001f3ca\U0001f3fb",
[":swimmer::skin-tone-1:"] = "\U0001f3ca\U0001f3fb",
[":person_swimming_tone2:"] = "\U0001f3ca\U0001f3fc",
[":swimmer_tone2:"] = "\U0001f3ca\U0001f3fc",
[":person_swimming::skin-tone-2:"] = "\U0001f3ca\U0001f3fc",
[":swimmer::skin-tone-2:"] = "\U0001f3ca\U0001f3fc",
[":person_swimming_tone3:"] = "\U0001f3ca\U0001f3fd",
[":swimmer_tone3:"] = "\U0001f3ca\U0001f3fd",
[":person_swimming::skin-tone-3:"] = "\U0001f3ca\U0001f3fd",
[":swimmer::skin-tone-3:"] = "\U0001f3ca\U0001f3fd",
[":person_swimming_tone4:"] = "\U0001f3ca\U0001f3fe",
[":swimmer_tone4:"] = "\U0001f3ca\U0001f3fe",
[":person_swimming::skin-tone-4:"] = "\U0001f3ca\U0001f3fe",
[":swimmer::skin-tone-4:"] = "\U0001f3ca\U0001f3fe",
[":person_swimming_tone5:"] = "\U0001f3ca\U0001f3ff",
[":swimmer_tone5:"] = "\U0001f3ca\U0001f3ff",
[":person_swimming::skin-tone-5:"] = "\U0001f3ca\U0001f3ff",
[":swimmer::skin-tone-5:"] = "\U0001f3ca\U0001f3ff",
[":person_tipping_hand:"] = "\U0001f481",
[":information_desk_person:"] = "\U0001f481",
[":person_tipping_hand_tone1:"] = "\U0001f481\U0001f3fb",
[":information_desk_person_tone1:"] = "\U0001f481\U0001f3fb",
[":person_tipping_hand::skin-tone-1:"] = "\U0001f481\U0001f3fb",
[":information_desk_person::skin-tone-1:"] = "\U0001f481\U0001f3fb",
[":person_tipping_hand_tone2:"] = "\U0001f481\U0001f3fc",
[":information_desk_person_tone2:"] = "\U0001f481\U0001f3fc",
[":person_tipping_hand::skin-tone-2:"] = "\U0001f481\U0001f3fc",
[":information_desk_person::skin-tone-2:"] = "\U0001f481\U0001f3fc",
[":person_tipping_hand_tone3:"] = "\U0001f481\U0001f3fd",
[":information_desk_person_tone3:"] = "\U0001f481\U0001f3fd",
[":person_tipping_hand::skin-tone-3:"] = "\U0001f481\U0001f3fd",
[":information_desk_person::skin-tone-3:"] = "\U0001f481\U0001f3fd",
[":person_tipping_hand_tone4:"] = "\U0001f481\U0001f3fe",
[":information_desk_person_tone4:"] = "\U0001f481\U0001f3fe",
[":person_tipping_hand::skin-tone-4:"] = "\U0001f481\U0001f3fe",
[":information_desk_person::skin-tone-4:"] = "\U0001f481\U0001f3fe",
[":person_tipping_hand_tone5:"] = "\U0001f481\U0001f3ff",
[":information_desk_person_tone5:"] = "\U0001f481\U0001f3ff",
[":person_tipping_hand::skin-tone-5:"] = "\U0001f481\U0001f3ff",
[":information_desk_person::skin-tone-5:"] = "\U0001f481\U0001f3ff",
[":person_tone1_bald:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f9b2",
[":person_light_skin_tone_bald:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f9b2",
[":person_bald::skin-tone-1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f9b2",
[":person_tone1_curly_hair:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f9b1",
[":person_light_skin_tone_curly_hair:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f9b1",
[":person_curly_hair::skin-tone-1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f9b1",
[":person_tone1_red_hair:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f9b0",
[":person_light_skin_tone_red_hair:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f9b0",
[":person_red_hair::skin-tone-1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f9b0",
[":person_tone1_white_hair:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f9b3",
[":person_light_skin_tone_white_hair:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f9b3",
[":person_white_hair::skin-tone-1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f9b3",
[":person_tone2_bald:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f9b2",
[":person_medium_light_skin_tone_bald:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f9b2",
[":person_bald::skin-tone-2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f9b2",
[":person_tone2_curly_hair:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f9b1",
[":person_medium_light_skin_tone_curly_hair:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f9b1",
[":person_curly_hair::skin-tone-2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f9b1",
[":person_tone2_red_hair:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f9b0",
[":person_medium_light_skin_tone_red_hair:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f9b0",
[":person_red_hair::skin-tone-2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f9b0",
[":person_tone2_white_hair:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f9b3",
[":person_medium_light_skin_tone_white_hair:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f9b3",
[":person_white_hair::skin-tone-2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f9b3",
[":person_tone3_bald:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f9b2",
[":person_medium_skin_tone_bald:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f9b2",
[":person_bald::skin-tone-3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f9b2",
[":person_tone3_curly_hair:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f9b1",
[":person_medium_skin_tone_curly_hair:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f9b1",
[":person_curly_hair::skin-tone-3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f9b1",
[":person_tone3_red_hair:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f9b0",
[":person_medium_skin_tone_red_hair:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f9b0",
[":person_red_hair::skin-tone-3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f9b0",
[":person_tone3_white_hair:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f9b3",
[":person_medium_skin_tone_white_hair:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f9b3",
[":person_white_hair::skin-tone-3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f9b3",
[":person_tone4_bald:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f9b2",
[":person_medium_dark_skin_tone_bald:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f9b2",
[":person_bald::skin-tone-4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f9b2",
[":person_tone4_curly_hair:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f9b1",
[":person_medium_dark_skin_tone_curly_hair:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f9b1",
[":person_curly_hair::skin-tone-4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f9b1",
[":person_tone4_red_hair:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f9b0",
[":person_medium_dark_skin_tone_red_hair:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f9b0",
[":person_red_hair::skin-tone-4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f9b0",
[":person_tone4_white_hair:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f9b3",
[":person_medium_dark_skin_tone_white_hair:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f9b3",
[":person_white_hair::skin-tone-4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f9b3",
[":person_tone5_bald:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f9b2",
[":person_dark_skin_tone_bald:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f9b2",
[":person_bald::skin-tone-5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f9b2",
[":person_tone5_curly_hair:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f9b1",
[":person_dark_skin_tone_curly_hair:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f9b1",
[":person_curly_hair::skin-tone-5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f9b1",
[":person_tone5_red_hair:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f9b0",
[":person_dark_skin_tone_red_hair:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f9b0",
[":person_red_hair::skin-tone-5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f9b0",
[":person_tone5_white_hair:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f9b3",
[":person_dark_skin_tone_white_hair:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f9b3",
[":person_white_hair::skin-tone-5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f9b3",
[":person_walking:"] = "\U0001f6b6",
[":walking:"] = "\U0001f6b6",
[":person_walking_tone1:"] = "\U0001f6b6\U0001f3fb",
[":walking_tone1:"] = "\U0001f6b6\U0001f3fb",
[":person_walking::skin-tone-1:"] = "\U0001f6b6\U0001f3fb",
[":walking::skin-tone-1:"] = "\U0001f6b6\U0001f3fb",
[":person_walking_tone2:"] = "\U0001f6b6\U0001f3fc",
[":walking_tone2:"] = "\U0001f6b6\U0001f3fc",
[":person_walking::skin-tone-2:"] = "\U0001f6b6\U0001f3fc",
[":walking::skin-tone-2:"] = "\U0001f6b6\U0001f3fc",
[":person_walking_tone3:"] = "\U0001f6b6\U0001f3fd",
[":walking_tone3:"] = "\U0001f6b6\U0001f3fd",
[":person_walking::skin-tone-3:"] = "\U0001f6b6\U0001f3fd",
[":walking::skin-tone-3:"] = "\U0001f6b6\U0001f3fd",
[":person_walking_tone4:"] = "\U0001f6b6\U0001f3fe",
[":walking_tone4:"] = "\U0001f6b6\U0001f3fe",
[":person_walking::skin-tone-4:"] = "\U0001f6b6\U0001f3fe",
[":walking::skin-tone-4:"] = "\U0001f6b6\U0001f3fe",
[":person_walking_tone5:"] = "\U0001f6b6\U0001f3ff",
[":walking_tone5:"] = "\U0001f6b6\U0001f3ff",
[":person_walking::skin-tone-5:"] = "\U0001f6b6\U0001f3ff",
[":walking::skin-tone-5:"] = "\U0001f6b6\U0001f3ff",
[":person_wearing_turban:"] = "\U0001f473",
[":man_with_turban:"] = "\U0001f473",
[":person_wearing_turban_tone1:"] = "\U0001f473\U0001f3fb",
[":man_with_turban_tone1:"] = "\U0001f473\U0001f3fb",
[":person_wearing_turban::skin-tone-1:"] = "\U0001f473\U0001f3fb",
[":man_with_turban::skin-tone-1:"] = "\U0001f473\U0001f3fb",
[":person_wearing_turban_tone2:"] = "\U0001f473\U0001f3fc",
[":man_with_turban_tone2:"] = "\U0001f473\U0001f3fc",
[":person_wearing_turban::skin-tone-2:"] = "\U0001f473\U0001f3fc",
[":man_with_turban::skin-tone-2:"] = "\U0001f473\U0001f3fc",
[":person_wearing_turban_tone3:"] = "\U0001f473\U0001f3fd",
[":man_with_turban_tone3:"] = "\U0001f473\U0001f3fd",
[":person_wearing_turban::skin-tone-3:"] = "\U0001f473\U0001f3fd",
[":man_with_turban::skin-tone-3:"] = "\U0001f473\U0001f3fd",
[":person_wearing_turban_tone4:"] = "\U0001f473\U0001f3fe",
[":man_with_turban_tone4:"] = "\U0001f473\U0001f3fe",
[":person_wearing_turban::skin-tone-4:"] = "\U0001f473\U0001f3fe",
[":man_with_turban::skin-tone-4:"] = "\U0001f473\U0001f3fe",
[":person_wearing_turban_tone5:"] = "\U0001f473\U0001f3ff",
[":man_with_turban_tone5:"] = "\U0001f473\U0001f3ff",
[":person_wearing_turban::skin-tone-5:"] = "\U0001f473\U0001f3ff",
[":man_with_turban::skin-tone-5:"] = "\U0001f473\U0001f3ff",
[":person_white_hair:"] = "\U0001f9d1\u200d\U0001f9b3",
[":person_with_probing_cane:"] = "\U0001f9d1\u200d\U0001f9af",
[":person_with_probing_cane_tone1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f9af",
[":person_with_probing_cane_light_skin_tone:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f9af",
[":person_with_probing_cane::skin-tone-1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f9af",
[":person_with_probing_cane_tone2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f9af",
[":person_with_probing_cane_medium_light_skin_tone:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f9af",
[":person_with_probing_cane::skin-tone-2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f9af",
[":person_with_probing_cane_tone3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f9af",
[":person_with_probing_cane_medium_skin_tone:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f9af",
[":person_with_probing_cane::skin-tone-3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f9af",
[":person_with_probing_cane_tone4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f9af",
[":person_with_probing_cane_medium_dark_skin_tone:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f9af",
[":person_with_probing_cane::skin-tone-4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f9af",
[":person_with_probing_cane_tone5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f9af",
[":person_with_probing_cane_dark_skin_tone:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f9af",
[":person_with_probing_cane::skin-tone-5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f9af",
[":person_with_veil:"] = "\U0001f470",
[":person_with_veil_tone1:"] = "\U0001f470\U0001f3fb",
[":person_with_veil::skin-tone-1:"] = "\U0001f470\U0001f3fb",
[":person_with_veil_tone2:"] = "\U0001f470\U0001f3fc",
[":person_with_veil::skin-tone-2:"] = "\U0001f470\U0001f3fc",
[":person_with_veil_tone3:"] = "\U0001f470\U0001f3fd",
[":person_with_veil::skin-tone-3:"] = "\U0001f470\U0001f3fd",
[":person_with_veil_tone4:"] = "\U0001f470\U0001f3fe",
[":person_with_veil::skin-tone-4:"] = "\U0001f470\U0001f3fe",
[":person_with_veil_tone5:"] = "\U0001f470\U0001f3ff",
[":person_with_veil::skin-tone-5:"] = "\U0001f470\U0001f3ff",
[":petri_dish:"] = "\U0001f9eb",
[":pick:"] = "\u26cf\ufe0f",
[":pickup_truck:"] = "\U0001f6fb",
[":pie:"] = "\U0001f967",
[":pig:"] = "\U0001f437",
[":pig_nose:"] = "\U0001f43d",
[":pig2:"] = "\U0001f416",
[":pill:"] = "\U0001f48a",
[":pilot:"] = "\U0001f9d1\u200d\u2708\ufe0f",
[":pilot_tone1:"] = "\U0001f9d1\U0001f3fb\u200d\u2708\ufe0f",
[":pilot_light_skin_tone:"] = "\U0001f9d1\U0001f3fb\u200d\u2708\ufe0f",
[":pilot::skin-tone-1:"] = "\U0001f9d1\U0001f3fb\u200d\u2708\ufe0f",
[":pilot_tone2:"] = "\U0001f9d1\U0001f3fc\u200d\u2708\ufe0f",
[":pilot_medium_light_skin_tone:"] = "\U0001f9d1\U0001f3fc\u200d\u2708\ufe0f",
[":pilot::skin-tone-2:"] = "\U0001f9d1\U0001f3fc\u200d\u2708\ufe0f",
[":pilot_tone3:"] = "\U0001f9d1\U0001f3fd\u200d\u2708\ufe0f",
[":pilot_medium_skin_tone:"] = "\U0001f9d1\U0001f3fd\u200d\u2708\ufe0f",
[":pilot::skin-tone-3:"] = "\U0001f9d1\U0001f3fd\u200d\u2708\ufe0f",
[":pilot_tone4:"] = "\U0001f9d1\U0001f3fe\u200d\u2708\ufe0f",
[":pilot_medium_dark_skin_tone:"] = "\U0001f9d1\U0001f3fe\u200d\u2708\ufe0f",
[":pilot::skin-tone-4:"] = "\U0001f9d1\U0001f3fe\u200d\u2708\ufe0f",
[":pilot_tone5:"] = "\U0001f9d1\U0001f3ff\u200d\u2708\ufe0f",
[":pilot_dark_skin_tone:"] = "\U0001f9d1\U0001f3ff\u200d\u2708\ufe0f",
[":pilot::skin-tone-5:"] = "\U0001f9d1\U0001f3ff\u200d\u2708\ufe0f",
[":piñata:"] = "\U0001fa85",
[":pinched_fingers:"] = "\U0001f90c",
[":pinched_fingers_tone1:"] = "\U0001f90c\U0001f3fb",
[":pinched_fingers_light_skin_tone:"] = "\U0001f90c\U0001f3fb",
[":pinched_fingers::skin-tone-1:"] = "\U0001f90c\U0001f3fb",
[":pinched_fingers_tone2:"] = "\U0001f90c\U0001f3fc",
[":pinched_fingers_medium_light_skin_tone:"] = "\U0001f90c\U0001f3fc",
[":pinched_fingers::skin-tone-2:"] = "\U0001f90c\U0001f3fc",
[":pinched_fingers_tone3:"] = "\U0001f90c\U0001f3fd",
[":pinched_fingers_medium_skin_tone:"] = "\U0001f90c\U0001f3fd",
[":pinched_fingers::skin-tone-3:"] = "\U0001f90c\U0001f3fd",
[":pinched_fingers_tone4:"] = "\U0001f90c\U0001f3fe",
[":pinched_fingers_medium_dark_skin_tone:"] = "\U0001f90c\U0001f3fe",
[":pinched_fingers::skin-tone-4:"] = "\U0001f90c\U0001f3fe",
[":pinched_fingers_tone5:"] = "\U0001f90c\U0001f3ff",
[":pinched_fingers_dark_skin_tone:"] = "\U0001f90c\U0001f3ff",
[":pinched_fingers::skin-tone-5:"] = "\U0001f90c\U0001f3ff",
[":pinching_hand:"] = "\U0001f90f",
[":pinching_hand_tone1:"] = "\U0001f90f\U0001f3fb",
[":pinching_hand_light_skin_tone:"] = "\U0001f90f\U0001f3fb",
[":pinching_hand::skin-tone-1:"] = "\U0001f90f\U0001f3fb",
[":pinching_hand_tone2:"] = "\U0001f90f\U0001f3fc",
[":pinching_hand_medium_light_skin_tone:"] = "\U0001f90f\U0001f3fc",
[":pinching_hand::skin-tone-2:"] = "\U0001f90f\U0001f3fc",
[":pinching_hand_tone3:"] = "\U0001f90f\U0001f3fd",
[":pinching_hand_medium_skin_tone:"] = "\U0001f90f\U0001f3fd",
[":pinching_hand::skin-tone-3:"] = "\U0001f90f\U0001f3fd",
[":pinching_hand_tone4:"] = "\U0001f90f\U0001f3fe",
[":pinching_hand_medium_dark_skin_tone:"] = "\U0001f90f\U0001f3fe",
[":pinching_hand::skin-tone-4:"] = "\U0001f90f\U0001f3fe",
[":pinching_hand_tone5:"] = "\U0001f90f\U0001f3ff",
[":pinching_hand_dark_skin_tone:"] = "\U0001f90f\U0001f3ff",
[":pinching_hand::skin-tone-5:"] = "\U0001f90f\U0001f3ff",
[":pineapple:"] = "\U0001f34d",
[":ping_pong:"] = "\U0001f3d3",
[":table_tennis:"] = "\U0001f3d3",
[":pirate_flag:"] = "\U0001f3f4\u200d\u2620\ufe0f",
[":pisces:"] = "\u2653",
[":pizza:"] = "\U0001f355",
[":placard:"] = "\U0001faa7",
[":place_of_worship:"] = "\U0001f6d0",
[":worship_symbol:"] = "\U0001f6d0",
[":play_pause:"] = "\u23ef\ufe0f",
[":pleading_face:"] = "\U0001f97a",
[":plunger:"] = "\U0001faa0",
[":point_down:"] = "\U0001f447",
[":point_down_tone1:"] = "\U0001f447\U0001f3fb",
[":point_down::skin-tone-1:"] = "\U0001f447\U0001f3fb",
[":point_down_tone2:"] = "\U0001f447\U0001f3fc",
[":point_down::skin-tone-2:"] = "\U0001f447\U0001f3fc",
[":point_down_tone3:"] = "\U0001f447\U0001f3fd",
[":point_down::skin-tone-3:"] = "\U0001f447\U0001f3fd",
[":point_down_tone4:"] = "\U0001f447\U0001f3fe",
[":point_down::skin-tone-4:"] = "\U0001f447\U0001f3fe",
[":point_down_tone5:"] = "\U0001f447\U0001f3ff",
[":point_down::skin-tone-5:"] = "\U0001f447\U0001f3ff",
[":point_left:"] = "\U0001f448",
[":point_left_tone1:"] = "\U0001f448\U0001f3fb",
[":point_left::skin-tone-1:"] = "\U0001f448\U0001f3fb",
[":point_left_tone2:"] = "\U0001f448\U0001f3fc",
[":point_left::skin-tone-2:"] = "\U0001f448\U0001f3fc",
[":point_left_tone3:"] = "\U0001f448\U0001f3fd",
[":point_left::skin-tone-3:"] = "\U0001f448\U0001f3fd",
[":point_left_tone4:"] = "\U0001f448\U0001f3fe",
[":point_left::skin-tone-4:"] = "\U0001f448\U0001f3fe",
[":point_left_tone5:"] = "\U0001f448\U0001f3ff",
[":point_left::skin-tone-5:"] = "\U0001f448\U0001f3ff",
[":point_right:"] = "\U0001f449",
[":point_right_tone1:"] = "\U0001f449\U0001f3fb",
[":point_right::skin-tone-1:"] = "\U0001f449\U0001f3fb",
[":point_right_tone2:"] = "\U0001f449\U0001f3fc",
[":point_right::skin-tone-2:"] = "\U0001f449\U0001f3fc",
[":point_right_tone3:"] = "\U0001f449\U0001f3fd",
[":point_right::skin-tone-3:"] = "\U0001f449\U0001f3fd",
[":point_right_tone4:"] = "\U0001f449\U0001f3fe",
[":point_right::skin-tone-4:"] = "\U0001f449\U0001f3fe",
[":point_right_tone5:"] = "\U0001f449\U0001f3ff",
[":point_right::skin-tone-5:"] = "\U0001f449\U0001f3ff",
[":point_up:"] = "\u261d\ufe0f",
[":point_up_2:"] = "\U0001f446",
[":point_up_2_tone1:"] = "\U0001f446\U0001f3fb",
[":point_up_2::skin-tone-1:"] = "\U0001f446\U0001f3fb",
[":point_up_2_tone2:"] = "\U0001f446\U0001f3fc",
[":point_up_2::skin-tone-2:"] = "\U0001f446\U0001f3fc",
[":point_up_2_tone3:"] = "\U0001f446\U0001f3fd",
[":point_up_2::skin-tone-3:"] = "\U0001f446\U0001f3fd",
[":point_up_2_tone4:"] = "\U0001f446\U0001f3fe",
[":point_up_2::skin-tone-4:"] = "\U0001f446\U0001f3fe",
[":point_up_2_tone5:"] = "\U0001f446\U0001f3ff",
[":point_up_2::skin-tone-5:"] = "\U0001f446\U0001f3ff",
[":point_up_tone1:"] = "\u261d\U0001f3fb",
[":point_up::skin-tone-1:"] = "\u261d\U0001f3fb",
[":point_up_tone2:"] = "\u261d\U0001f3fc",
[":point_up::skin-tone-2:"] = "\u261d\U0001f3fc",
[":point_up_tone3:"] = "\u261d\U0001f3fd",
[":point_up::skin-tone-3:"] = "\u261d\U0001f3fd",
[":point_up_tone4:"] = "\u261d\U0001f3fe",
[":point_up::skin-tone-4:"] = "\u261d\U0001f3fe",
[":point_up_tone5:"] = "\u261d\U0001f3ff",
[":point_up::skin-tone-5:"] = "\u261d\U0001f3ff",
[":polar_bear:"] = "\U0001f43b\u200d\u2744\ufe0f",
[":police_car:"] = "\U0001f693",
[":police_officer:"] = "\U0001f46e",
[":cop:"] = "\U0001f46e",
[":police_officer_tone1:"] = "\U0001f46e\U0001f3fb",
[":cop_tone1:"] = "\U0001f46e\U0001f3fb",
[":police_officer::skin-tone-1:"] = "\U0001f46e\U0001f3fb",
[":cop::skin-tone-1:"] = "\U0001f46e\U0001f3fb",
[":police_officer_tone2:"] = "\U0001f46e\U0001f3fc",
[":cop_tone2:"] = "\U0001f46e\U0001f3fc",
[":police_officer::skin-tone-2:"] = "\U0001f46e\U0001f3fc",
[":cop::skin-tone-2:"] = "\U0001f46e\U0001f3fc",
[":police_officer_tone3:"] = "\U0001f46e\U0001f3fd",
[":cop_tone3:"] = "\U0001f46e\U0001f3fd",
[":police_officer::skin-tone-3:"] = "\U0001f46e\U0001f3fd",
[":cop::skin-tone-3:"] = "\U0001f46e\U0001f3fd",
[":police_officer_tone4:"] = "\U0001f46e\U0001f3fe",
[":cop_tone4:"] = "\U0001f46e\U0001f3fe",
[":police_officer::skin-tone-4:"] = "\U0001f46e\U0001f3fe",
[":cop::skin-tone-4:"] = "\U0001f46e\U0001f3fe",
[":police_officer_tone5:"] = "\U0001f46e\U0001f3ff",
[":cop_tone5:"] = "\U0001f46e\U0001f3ff",
[":police_officer::skin-tone-5:"] = "\U0001f46e\U0001f3ff",
[":cop::skin-tone-5:"] = "\U0001f46e\U0001f3ff",
[":poodle:"] = "\U0001f429",
[":poop:"] = "\U0001f4a9",
[":shit:"] = "\U0001f4a9",
[":hankey:"] = "\U0001f4a9",
[":poo:"] = "\U0001f4a9",
[":popcorn:"] = "\U0001f37f",
[":post_office:"] = "\U0001f3e3",
[":postal_horn:"] = "\U0001f4ef",
[":postbox:"] = "\U0001f4ee",
[":potable_water:"] = "\U0001f6b0",
[":potato:"] = "\U0001f954",
[":potted_plant:"] = "\U0001fab4",
[":pouch:"] = "\U0001f45d",
[":poultry_leg:"] = "\U0001f357",
[":pound:"] = "\U0001f4b7",
[":pouting_cat:"] = "\U0001f63e",
[":pray:"] = "\U0001f64f",
[":pray_tone1:"] = "\U0001f64f\U0001f3fb",
[":pray::skin-tone-1:"] = "\U0001f64f\U0001f3fb",
[":pray_tone2:"] = "\U0001f64f\U0001f3fc",
[":pray::skin-tone-2:"] = "\U0001f64f\U0001f3fc",
[":pray_tone3:"] = "\U0001f64f\U0001f3fd",
[":pray::skin-tone-3:"] = "\U0001f64f\U0001f3fd",
[":pray_tone4:"] = "\U0001f64f\U0001f3fe",
[":pray::skin-tone-4:"] = "\U0001f64f\U0001f3fe",
[":pray_tone5:"] = "\U0001f64f\U0001f3ff",
[":pray::skin-tone-5:"] = "\U0001f64f\U0001f3ff",
[":prayer_beads:"] = "\U0001f4ff",
[":pregnant_woman:"] = "\U0001f930",
[":expecting_woman:"] = "\U0001f930",
[":pregnant_woman_tone1:"] = "\U0001f930\U0001f3fb",
[":expecting_woman_tone1:"] = "\U0001f930\U0001f3fb",
[":pregnant_woman::skin-tone-1:"] = "\U0001f930\U0001f3fb",
[":expecting_woman::skin-tone-1:"] = "\U0001f930\U0001f3fb",
[":pregnant_woman_tone2:"] = "\U0001f930\U0001f3fc",
[":expecting_woman_tone2:"] = "\U0001f930\U0001f3fc",
[":pregnant_woman::skin-tone-2:"] = "\U0001f930\U0001f3fc",
[":expecting_woman::skin-tone-2:"] = "\U0001f930\U0001f3fc",
[":pregnant_woman_tone3:"] = "\U0001f930\U0001f3fd",
[":expecting_woman_tone3:"] = "\U0001f930\U0001f3fd",
[":pregnant_woman::skin-tone-3:"] = "\U0001f930\U0001f3fd",
[":expecting_woman::skin-tone-3:"] = "\U0001f930\U0001f3fd",
[":pregnant_woman_tone4:"] = "\U0001f930\U0001f3fe",
[":expecting_woman_tone4:"] = "\U0001f930\U0001f3fe",
[":pregnant_woman::skin-tone-4:"] = "\U0001f930\U0001f3fe",
[":expecting_woman::skin-tone-4:"] = "\U0001f930\U0001f3fe",
[":pregnant_woman_tone5:"] = "\U0001f930\U0001f3ff",
[":expecting_woman_tone5:"] = "\U0001f930\U0001f3ff",
[":pregnant_woman::skin-tone-5:"] = "\U0001f930\U0001f3ff",
[":expecting_woman::skin-tone-5:"] = "\U0001f930\U0001f3ff",
[":pretzel:"] = "\U0001f968",
[":prince:"] = "\U0001f934",
[":prince_tone1:"] = "\U0001f934\U0001f3fb",
[":prince::skin-tone-1:"] = "\U0001f934\U0001f3fb",
[":prince_tone2:"] = "\U0001f934\U0001f3fc",
[":prince::skin-tone-2:"] = "\U0001f934\U0001f3fc",
[":prince_tone3:"] = "\U0001f934\U0001f3fd",
[":prince::skin-tone-3:"] = "\U0001f934\U0001f3fd",
[":prince_tone4:"] = "\U0001f934\U0001f3fe",
[":prince::skin-tone-4:"] = "\U0001f934\U0001f3fe",
[":prince_tone5:"] = "\U0001f934\U0001f3ff",
[":prince::skin-tone-5:"] = "\U0001f934\U0001f3ff",
[":princess:"] = "\U0001f478",
[":princess_tone1:"] = "\U0001f478\U0001f3fb",
[":princess::skin-tone-1:"] = "\U0001f478\U0001f3fb",
[":princess_tone2:"] = "\U0001f478\U0001f3fc",
[":princess::skin-tone-2:"] = "\U0001f478\U0001f3fc",
[":princess_tone3:"] = "\U0001f478\U0001f3fd",
[":princess::skin-tone-3:"] = "\U0001f478\U0001f3fd",
[":princess_tone4:"] = "\U0001f478\U0001f3fe",
[":princess::skin-tone-4:"] = "\U0001f478\U0001f3fe",
[":princess_tone5:"] = "\U0001f478\U0001f3ff",
[":princess::skin-tone-5:"] = "\U0001f478\U0001f3ff",
[":printer:"] = "\U0001f5a8\ufe0f",
[":probing_cane:"] = "\U0001f9af",
[":projector:"] = "\U0001f4fd\ufe0f",
[":film_projector:"] = "\U0001f4fd\ufe0f",
[":punch:"] = "\U0001f44a",
[":punch_tone1:"] = "\U0001f44a\U0001f3fb",
[":punch::skin-tone-1:"] = "\U0001f44a\U0001f3fb",
[":punch_tone2:"] = "\U0001f44a\U0001f3fc",
[":punch::skin-tone-2:"] = "\U0001f44a\U0001f3fc",
[":punch_tone3:"] = "\U0001f44a\U0001f3fd",
[":punch::skin-tone-3:"] = "\U0001f44a\U0001f3fd",
[":punch_tone4:"] = "\U0001f44a\U0001f3fe",
[":punch::skin-tone-4:"] = "\U0001f44a\U0001f3fe",
[":punch_tone5:"] = "\U0001f44a\U0001f3ff",
[":punch::skin-tone-5:"] = "\U0001f44a\U0001f3ff",
[":purple_circle:"] = "\U0001f7e3",
[":purple_heart:"] = "\U0001f49c",
[":purple_square:"] = "\U0001f7ea",
[":purse:"] = "\U0001f45b",
[":pushpin:"] = "\U0001f4cc",
[":put_litter_in_its_place:"] = "\U0001f6ae",
[":question:"] = "\u2753",
[":rabbit:"] = "\U0001f430",
[":rabbit2:"] = "\U0001f407",
[":raccoon:"] = "\U0001f99d",
[":race_car:"] = "\U0001f3ce\ufe0f",
[":racing_car:"] = "\U0001f3ce\ufe0f",
[":racehorse:"] = "\U0001f40e",
[":radio:"] = "\U0001f4fb",
[":radio_button:"] = "\U0001f518",
[":radioactive:"] = "\u2622\ufe0f",
[":radioactive_sign:"] = "\u2622\ufe0f",
[":rage:"] = "\U0001f621",
[":@"] = "\U0001f621",
[":-@"] = "\U0001f621",
["=@"] = "\U0001f621",
["=-@"] = "\U0001f621",
[":railway_car:"] = "\U0001f683",
[":railway_track:"] = "\U0001f6e4\ufe0f",
[":railroad_track:"] = "\U0001f6e4\ufe0f",
[":rainbow:"] = "\U0001f308",
[":rainbow_flag:"] = "\U0001f3f3\ufe0f\u200d\U0001f308",
[":gay_pride_flag:"] = "\U0001f3f3\ufe0f\u200d\U0001f308",
[":raised_back_of_hand:"] = "\U0001f91a",
[":back_of_hand:"] = "\U0001f91a",
[":raised_back_of_hand_tone1:"] = "\U0001f91a\U0001f3fb",
[":back_of_hand_tone1:"] = "\U0001f91a\U0001f3fb",
[":raised_back_of_hand::skin-tone-1:"] = "\U0001f91a\U0001f3fb",
[":back_of_hand::skin-tone-1:"] = "\U0001f91a\U0001f3fb",
[":raised_back_of_hand_tone2:"] = "\U0001f91a\U0001f3fc",
[":back_of_hand_tone2:"] = "\U0001f91a\U0001f3fc",
[":raised_back_of_hand::skin-tone-2:"] = "\U0001f91a\U0001f3fc",
[":back_of_hand::skin-tone-2:"] = "\U0001f91a\U0001f3fc",
[":raised_back_of_hand_tone3:"] = "\U0001f91a\U0001f3fd",
[":back_of_hand_tone3:"] = "\U0001f91a\U0001f3fd",
[":raised_back_of_hand::skin-tone-3:"] = "\U0001f91a\U0001f3fd",
[":back_of_hand::skin-tone-3:"] = "\U0001f91a\U0001f3fd",
[":raised_back_of_hand_tone4:"] = "\U0001f91a\U0001f3fe",
[":back_of_hand_tone4:"] = "\U0001f91a\U0001f3fe",
[":raised_back_of_hand::skin-tone-4:"] = "\U0001f91a\U0001f3fe",
[":back_of_hand::skin-tone-4:"] = "\U0001f91a\U0001f3fe",
[":raised_back_of_hand_tone5:"] = "\U0001f91a\U0001f3ff",
[":back_of_hand_tone5:"] = "\U0001f91a\U0001f3ff",
[":raised_back_of_hand::skin-tone-5:"] = "\U0001f91a\U0001f3ff",
[":back_of_hand::skin-tone-5:"] = "\U0001f91a\U0001f3ff",
[":raised_hand:"] = "\u270b",
[":raised_hand_tone1:"] = "\u270b\U0001f3fb",
[":raised_hand::skin-tone-1:"] = "\u270b\U0001f3fb",
[":raised_hand_tone2:"] = "\u270b\U0001f3fc",
[":raised_hand::skin-tone-2:"] = "\u270b\U0001f3fc",
[":raised_hand_tone3:"] = "\u270b\U0001f3fd",
[":raised_hand::skin-tone-3:"] = "\u270b\U0001f3fd",
[":raised_hand_tone4:"] = "\u270b\U0001f3fe",
[":raised_hand::skin-tone-4:"] = "\u270b\U0001f3fe",
[":raised_hand_tone5:"] = "\u270b\U0001f3ff",
[":raised_hand::skin-tone-5:"] = "\u270b\U0001f3ff",
[":raised_hands:"] = "\U0001f64c",
[":raised_hands_tone1:"] = "\U0001f64c\U0001f3fb",
[":raised_hands::skin-tone-1:"] = "\U0001f64c\U0001f3fb",
[":raised_hands_tone2:"] = "\U0001f64c\U0001f3fc",
[":raised_hands::skin-tone-2:"] = "\U0001f64c\U0001f3fc",
[":raised_hands_tone3:"] = "\U0001f64c\U0001f3fd",
[":raised_hands::skin-tone-3:"] = "\U0001f64c\U0001f3fd",
[":raised_hands_tone4:"] = "\U0001f64c\U0001f3fe",
[":raised_hands::skin-tone-4:"] = "\U0001f64c\U0001f3fe",
[":raised_hands_tone5:"] = "\U0001f64c\U0001f3ff",
[":raised_hands::skin-tone-5:"] = "\U0001f64c\U0001f3ff",
[":ram:"] = "\U0001f40f",
[":ramen:"] = "\U0001f35c",
[":rat:"] = "\U0001f400",
[":razor:"] = "\U0001fa92",
[":receipt:"] = "\U0001f9fe",
[":record_button:"] = "\u23fa\ufe0f",
[":recycle:"] = "\u267b\ufe0f",
[":red_car:"] = "\U0001f697",
[":red_circle:"] = "\U0001f534",
[":red_envelope:"] = "\U0001f9e7",
[":red_square:"] = "\U0001f7e5",
[":regional_indicator_a:"] = "\U0001f1e6",
[":regional_indicator_b:"] = "\U0001f1e7",
[":regional_indicator_c:"] = "\U0001f1e8",
[":regional_indicator_d:"] = "\U0001f1e9",
[":regional_indicator_e:"] = "\U0001f1ea",
[":regional_indicator_f:"] = "\U0001f1eb",
[":regional_indicator_g:"] = "\U0001f1ec",
[":regional_indicator_h:"] = "\U0001f1ed",
[":regional_indicator_i:"] = "\U0001f1ee",
[":regional_indicator_j:"] = "\U0001f1ef",
[":regional_indicator_k:"] = "\U0001f1f0",
[":regional_indicator_l:"] = "\U0001f1f1",
[":regional_indicator_m:"] = "\U0001f1f2",
[":regional_indicator_n:"] = "\U0001f1f3",
[":regional_indicator_o:"] = "\U0001f1f4",
[":regional_indicator_p:"] = "\U0001f1f5",
[":regional_indicator_q:"] = "\U0001f1f6",
[":regional_indicator_r:"] = "\U0001f1f7",
[":regional_indicator_s:"] = "\U0001f1f8",
[":regional_indicator_t:"] = "\U0001f1f9",
[":regional_indicator_u:"] = "\U0001f1fa",
[":regional_indicator_v:"] = "\U0001f1fb",
[":regional_indicator_w:"] = "\U0001f1fc",
[":regional_indicator_x:"] = "\U0001f1fd",
[":regional_indicator_y:"] = "\U0001f1fe",
[":regional_indicator_z:"] = "\U0001f1ff",
[":registered:"] = "\u00ae\ufe0f",
[":relaxed:"] = "\u263a\ufe0f",
[":relieved:"] = "\U0001f60c",
[":reminder_ribbon:"] = "\U0001f397\ufe0f",
[":repeat:"] = "\U0001f501",
[":repeat_one:"] = "\U0001f502",
[":restroom:"] = "\U0001f6bb",
[":revolving_hearts:"] = "\U0001f49e",
[":rewind:"] = "\u23ea",
[":rhino:"] = "\U0001f98f",
[":rhinoceros:"] = "\U0001f98f",
[":ribbon:"] = "\U0001f380",
[":rice:"] = "\U0001f35a",
[":rice_ball:"] = "\U0001f359",
[":rice_cracker:"] = "\U0001f358",
[":rice_scene:"] = "\U0001f391",
[":right_facing_fist:"] = "\U0001f91c",
[":right_fist:"] = "\U0001f91c",
[":right_facing_fist_tone1:"] = "\U0001f91c\U0001f3fb",
[":right_fist_tone1:"] = "\U0001f91c\U0001f3fb",
[":right_facing_fist::skin-tone-1:"] = "\U0001f91c\U0001f3fb",
[":right_fist::skin-tone-1:"] = "\U0001f91c\U0001f3fb",
[":right_facing_fist_tone2:"] = "\U0001f91c\U0001f3fc",
[":right_fist_tone2:"] = "\U0001f91c\U0001f3fc",
[":right_facing_fist::skin-tone-2:"] = "\U0001f91c\U0001f3fc",
[":right_fist::skin-tone-2:"] = "\U0001f91c\U0001f3fc",
[":right_facing_fist_tone3:"] = "\U0001f91c\U0001f3fd",
[":right_fist_tone3:"] = "\U0001f91c\U0001f3fd",
[":right_facing_fist::skin-tone-3:"] = "\U0001f91c\U0001f3fd",
[":right_fist::skin-tone-3:"] = "\U0001f91c\U0001f3fd",
[":right_facing_fist_tone4:"] = "\U0001f91c\U0001f3fe",
[":right_fist_tone4:"] = "\U0001f91c\U0001f3fe",
[":right_facing_fist::skin-tone-4:"] = "\U0001f91c\U0001f3fe",
[":right_fist::skin-tone-4:"] = "\U0001f91c\U0001f3fe",
[":right_facing_fist_tone5:"] = "\U0001f91c\U0001f3ff",
[":right_fist_tone5:"] = "\U0001f91c\U0001f3ff",
[":right_facing_fist::skin-tone-5:"] = "\U0001f91c\U0001f3ff",
[":right_fist::skin-tone-5:"] = "\U0001f91c\U0001f3ff",
[":ring:"] = "\U0001f48d",
[":ringed_planet:"] = "\U0001fa90",
[":robot:"] = "\U0001f916",
[":robot_face:"] = "\U0001f916",
[":rock:"] = "\U0001faa8",
[":rocket:"] = "\U0001f680",
[":rofl:"] = "\U0001f923",
[":rolling_on_the_floor_laughing:"] = "\U0001f923",
[":roll_of_paper:"] = "\U0001f9fb",
[":roller_coaster:"] = "\U0001f3a2",
[":roller_skate:"] = "\U0001f6fc",
[":rolling_eyes:"] = "\U0001f644",
[":face_with_rolling_eyes:"] = "\U0001f644",
[":rooster:"] = "\U0001f413",
[":rose:"] = "\U0001f339",
[":rosette:"] = "\U0001f3f5\ufe0f",
[":rotating_light:"] = "\U0001f6a8",
[":round_pushpin:"] = "\U0001f4cd",
[":rugby_football:"] = "\U0001f3c9",
[":running_shirt_with_sash:"] = "\U0001f3bd",
[":sa:"] = "\U0001f202\ufe0f",
[":safety_pin:"] = "\U0001f9f7",
[":safety_vest:"] = "\U0001f9ba",
[":sagittarius:"] = "\u2650",
[":sailboat:"] = "\u26f5",
[":sake:"] = "\U0001f376",
[":salad:"] = "\U0001f957",
[":green_salad:"] = "\U0001f957",
[":salt:"] = "\U0001f9c2",
[":sandal:"] = "\U0001f461",
[":sandwich:"] = "\U0001f96a",
[":santa:"] = "\U0001f385",
[":santa_tone1:"] = "\U0001f385\U0001f3fb",
[":santa::skin-tone-1:"] = "\U0001f385\U0001f3fb",
[":santa_tone2:"] = "\U0001f385\U0001f3fc",
[":santa::skin-tone-2:"] = "\U0001f385\U0001f3fc",
[":santa_tone3:"] = "\U0001f385\U0001f3fd",
[":santa::skin-tone-3:"] = "\U0001f385\U0001f3fd",
[":santa_tone4:"] = "\U0001f385\U0001f3fe",
[":santa::skin-tone-4:"] = "\U0001f385\U0001f3fe",
[":santa_tone5:"] = "\U0001f385\U0001f3ff",
[":santa::skin-tone-5:"] = "\U0001f385\U0001f3ff",
[":sari:"] = "\U0001f97b",
[":satellite:"] = "\U0001f4e1",
[":satellite_orbital:"] = "\U0001f6f0\ufe0f",
[":sauropod:"] = "\U0001f995",
[":saxophone:"] = "\U0001f3b7",
[":scales:"] = "\u2696\ufe0f",
[":scarf:"] = "\U0001f9e3",
[":school:"] = "\U0001f3eb",
[":school_satchel:"] = "\U0001f392",
[":scientist:"] = "\U0001f9d1\u200d\U0001f52c",
[":scientist_tone1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f52c",
[":scientist_light_skin_tone:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f52c",
[":scientist::skin-tone-1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f52c",
[":scientist_tone2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f52c",
[":scientist_medium_light_skin_tone:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f52c",
[":scientist::skin-tone-2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f52c",
[":scientist_tone3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f52c",
[":scientist_medium_skin_tone:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f52c",
[":scientist::skin-tone-3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f52c",
[":scientist_tone4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f52c",
[":scientist_medium_dark_skin_tone:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f52c",
[":scientist::skin-tone-4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f52c",
[":scientist_tone5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f52c",
[":scientist_dark_skin_tone:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f52c",
[":scientist::skin-tone-5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f52c",
[":scissors:"] = "\u2702\ufe0f",
[":scooter:"] = "\U0001f6f4",
[":scorpion:"] = "\U0001f982",
[":scorpius:"] = "\u264f",
[":scotland:"] = "\U0001f3f4\U000e0067\U000e0062\U000e0073\U000e0063\U000e0074\U000e007f",
[":scream:"] = "\U0001f631",
[":scream_cat:"] = "\U0001f640",
[":screwdriver:"] = "\U0001fa9b",
[":scroll:"] = "\U0001f4dc",
[":seal:"] = "\U0001f9ad",
[":seat:"] = "\U0001f4ba",
[":second_place:"] = "\U0001f948",
[":second_place_medal:"] = "\U0001f948",
[":secret:"] = "\u3299\ufe0f",
[":see_no_evil:"] = "\U0001f648",
[":seedling:"] = "\U0001f331",
[":selfie:"] = "\U0001f933",
[":selfie_tone1:"] = "\U0001f933\U0001f3fb",
[":selfie::skin-tone-1:"] = "\U0001f933\U0001f3fb",
[":selfie_tone2:"] = "\U0001f933\U0001f3fc",
[":selfie::skin-tone-2:"] = "\U0001f933\U0001f3fc",
[":selfie_tone3:"] = "\U0001f933\U0001f3fd",
[":selfie::skin-tone-3:"] = "\U0001f933\U0001f3fd",
[":selfie_tone4:"] = "\U0001f933\U0001f3fe",
[":selfie::skin-tone-4:"] = "\U0001f933\U0001f3fe",
[":selfie_tone5:"] = "\U0001f933\U0001f3ff",
[":selfie::skin-tone-5:"] = "\U0001f933\U0001f3ff",
[":service_dog:"] = "\U0001f415\u200d\U0001f9ba",
[":seven:"] = "\u0037\ufe0f\u20e3",
[":sewing_needle:"] = "\U0001faa1",
[":shallow_pan_of_food:"] = "\U0001f958",
[":paella:"] = "\U0001f958",
[":shamrock:"] = "\u2618\ufe0f",
[":shark:"] = "\U0001f988",
[":shaved_ice:"] = "\U0001f367",
[":sheep:"] = "\U0001f411",
[":shell:"] = "\U0001f41a",
[":shield:"] = "\U0001f6e1\ufe0f",
[":shinto_shrine:"] = "\u26e9\ufe0f",
[":ship:"] = "\U0001f6a2",
[":shirt:"] = "\U0001f455",
[":shopping_bags:"] = "\U0001f6cd\ufe0f",
[":shopping_cart:"] = "\U0001f6d2",
[":shopping_trolley:"] = "\U0001f6d2",
[":shorts:"] = "\U0001fa73",
[":shower:"] = "\U0001f6bf",
[":shrimp:"] = "\U0001f990",
[":shushing_face:"] = "\U0001f92b",
[":signal_strength:"] = "\U0001f4f6",
[":singer:"] = "\U0001f9d1\u200d\U0001f3a4",
[":singer_tone1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f3a4",
[":singer_light_skin_tone:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f3a4",
[":singer::skin-tone-1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f3a4",
[":singer_tone2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f3a4",
[":singer_medium_light_skin_tone:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f3a4",
[":singer::skin-tone-2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f3a4",
[":singer_tone3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f3a4",
[":singer_medium_skin_tone:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f3a4",
[":singer::skin-tone-3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f3a4",
[":singer_tone4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f3a4",
[":singer_medium_dark_skin_tone:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f3a4",
[":singer::skin-tone-4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f3a4",
[":singer_tone5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f3a4",
[":singer_dark_skin_tone:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f3a4",
[":singer::skin-tone-5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f3a4",
[":six:"] = "\u0036\ufe0f\u20e3",
[":six_pointed_star:"] = "\U0001f52f",
[":skateboard:"] = "\U0001f6f9",
[":ski:"] = "\U0001f3bf",
[":skier:"] = "\u26f7\ufe0f",
[":skull:"] = "\U0001f480",
[":skeleton:"] = "\U0001f480",
[":skull_crossbones:"] = "\u2620\ufe0f",
[":skull_and_crossbones:"] = "\u2620\ufe0f",
[":skunk:"] = "\U0001f9a8",
[":sled:"] = "\U0001f6f7",
[":sleeping:"] = "\U0001f634",
[":sleeping_accommodation:"] = "\U0001f6cc",
[":sleepy:"] = "\U0001f62a",
[":slight_frown:"] = "\U0001f641",
[":slightly_frowning_face:"] = "\U0001f641",
[":slight_smile:"] = "\U0001f642",
[":slightly_smiling_face:"] = "\U0001f642",
[":)"] = "\U0001f642",
[":-)"] = "\U0001f642",
["=)"] = "\U0001f642",
["=-)"] = "\U0001f642",
[":slot_machine:"] = "\U0001f3b0",
[":sloth:"] = "\U0001f9a5",
[":small_blue_diamond:"] = "\U0001f539",
[":small_orange_diamond:"] = "\U0001f538",
[":small_red_triangle:"] = "\U0001f53a",
[":small_red_triangle_down:"] = "\U0001f53b",
[":smile:"] = "\U0001f604",
[":D"] = "\U0001f604",
[":-D"] = "\U0001f604",
["=D"] = "\U0001f604",
["=-D"] = "\U0001f604",
[":smile_cat:"] = "\U0001f638",
[":smiley:"] = "\U0001f603",
[":smiley_cat:"] = "\U0001f63a",
[":smiling_face_with_3_hearts:"] = "\U0001f970",
[":smiling_face_with_tear:"] = "\U0001f972",
[":smiling_imp:"] = "\U0001f608",
["]:)"] = "\U0001f608",
["]:-)"] = "\U0001f608",
["]=)"] = "\U0001f608",
["]=-)"] = "\U0001f608",
[":smirk:"] = "\U0001f60f",
[":smirk_cat:"] = "\U0001f63c",
[":smoking:"] = "\U0001f6ac",
[":snail:"] = "\U0001f40c",
[":snake:"] = "\U0001f40d",
[":sneezing_face:"] = "\U0001f927",
[":sneeze:"] = "\U0001f927",
[":snowboarder:"] = "\U0001f3c2",
[":snowboarder_tone1:"] = "\U0001f3c2\U0001f3fb",
[":snowboarder_light_skin_tone:"] = "\U0001f3c2\U0001f3fb",
[":snowboarder::skin-tone-1:"] = "\U0001f3c2\U0001f3fb",
[":snowboarder_tone2:"] = "\U0001f3c2\U0001f3fc",
[":snowboarder_medium_light_skin_tone:"] = "\U0001f3c2\U0001f3fc",
[":snowboarder::skin-tone-2:"] = "\U0001f3c2\U0001f3fc",
[":snowboarder_tone3:"] = "\U0001f3c2\U0001f3fd",
[":snowboarder_medium_skin_tone:"] = "\U0001f3c2\U0001f3fd",
[":snowboarder::skin-tone-3:"] = "\U0001f3c2\U0001f3fd",
[":snowboarder_tone4:"] = "\U0001f3c2\U0001f3fe",
[":snowboarder_medium_dark_skin_tone:"] = "\U0001f3c2\U0001f3fe",
[":snowboarder::skin-tone-4:"] = "\U0001f3c2\U0001f3fe",
[":snowboarder_tone5:"] = "\U0001f3c2\U0001f3ff",
[":snowboarder_dark_skin_tone:"] = "\U0001f3c2\U0001f3ff",
[":snowboarder::skin-tone-5:"] = "\U0001f3c2\U0001f3ff",
[":snowflake:"] = "\u2744\ufe0f",
[":snowman:"] = "\u26c4",
[":snowman2:"] = "\u2603\ufe0f",
[":soap:"] = "\U0001f9fc",
[":sob:"] = "\U0001f62d",
[":,'("] = "\U0001f62d",
[":,'-("] = "\U0001f62d",
[";("] = "\U0001f62d",
[";-("] = "\U0001f62d",
["=,'("] = "\U0001f62d",
["=,'-("] = "\U0001f62d",
[":soccer:"] = "\u26bd",
[":socks:"] = "\U0001f9e6",
[":softball:"] = "\U0001f94e",
[":soon:"] = "\U0001f51c",
[":sos:"] = "\U0001f198",
[":sound:"] = "\U0001f509",
[":space_invader:"] = "\U0001f47e",
[":spades:"] = "\u2660\ufe0f",
[":spaghetti:"] = "\U0001f35d",
[":sparkle:"] = "\u2747\ufe0f",
[":sparkler:"] = "\U0001f387",
[":sparkles:"] = "\u2728",
[":sparkling_heart:"] = "\U0001f496",
[":speak_no_evil:"] = "\U0001f64a",
[":speaker:"] = "\U0001f508",
[":speaking_head:"] = "\U0001f5e3\ufe0f",
[":speaking_head_in_silhouette:"] = "\U0001f5e3\ufe0f",
[":speech_balloon:"] = "\U0001f4ac",
[":speech_left:"] = "\U0001f5e8\ufe0f",
[":left_speech_bubble:"] = "\U0001f5e8\ufe0f",
[":speedboat:"] = "\U0001f6a4",
[":spider:"] = "\U0001f577\ufe0f",
[":spider_web:"] = "\U0001f578\ufe0f",
[":sponge:"] = "\U0001f9fd",
[":spoon:"] = "\U0001f944",
[":squeeze_bottle:"] = "\U0001f9f4",
[":squid:"] = "\U0001f991",
[":stadium:"] = "\U0001f3df\ufe0f",
[":star:"] = "\u2b50",
[":star_and_crescent:"] = "\u262a\ufe0f",
[":star_of_david:"] = "\u2721\ufe0f",
[":star_struck:"] = "\U0001f929",
[":star2:"] = "\U0001f31f",
[":stars:"] = "\U0001f320",
[":station:"] = "\U0001f689",
[":statue_of_liberty:"] = "\U0001f5fd",
[":steam_locomotive:"] = "\U0001f682",
[":stethoscope:"] = "\U0001fa7a",
[":stew:"] = "\U0001f372",
[":stop_button:"] = "\u23f9\ufe0f",
[":stopwatch:"] = "\u23f1\ufe0f",
[":straight_ruler:"] = "\U0001f4cf",
[":strawberry:"] = "\U0001f353",
[":stuck_out_tongue:"] = "\U0001f61b",
[":P"] = "\U0001f61b",
[":-P"] = "\U0001f61b",
["=P"] = "\U0001f61b",
["=-P"] = "\U0001f61b",
[":stuck_out_tongue_closed_eyes:"] = "\U0001f61d",
[":stuck_out_tongue_winking_eye:"] = "\U0001f61c",
[":student:"] = "\U0001f9d1\u200d\U0001f393",
[":student_tone1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f393",
[":student_light_skin_tone:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f393",
[":student::skin-tone-1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f393",
[":student_tone2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f393",
[":student_medium_light_skin_tone:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f393",
[":student::skin-tone-2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f393",
[":student_tone3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f393",
[":student_medium_skin_tone:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f393",
[":student::skin-tone-3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f393",
[":student_tone4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f393",
[":student_medium_dark_skin_tone:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f393",
[":student::skin-tone-4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f393",
[":student_tone5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f393",
[":student_dark_skin_tone:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f393",
[":student::skin-tone-5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f393",
[":stuffed_flatbread:"] = "\U0001f959",
[":stuffed_pita:"] = "\U0001f959",
[":sun_with_face:"] = "\U0001f31e",
[":sunflower:"] = "\U0001f33b",
[":sunglasses:"] = "\U0001f60e",
["8-)"] = "\U0001f60e",
["B-)"] = "\U0001f60e",
[":sunny:"] = "\u2600\ufe0f",
[":sunrise:"] = "\U0001f305",
[":sunrise_over_mountains:"] = "\U0001f304",
[":superhero:"] = "\U0001f9b8",
[":superhero_tone1:"] = "\U0001f9b8\U0001f3fb",
[":superhero_light_skin_tone:"] = "\U0001f9b8\U0001f3fb",
[":superhero::skin-tone-1:"] = "\U0001f9b8\U0001f3fb",
[":superhero_tone2:"] = "\U0001f9b8\U0001f3fc",
[":superhero_medium_light_skin_tone:"] = "\U0001f9b8\U0001f3fc",
[":superhero::skin-tone-2:"] = "\U0001f9b8\U0001f3fc",
[":superhero_tone3:"] = "\U0001f9b8\U0001f3fd",
[":superhero_medium_skin_tone:"] = "\U0001f9b8\U0001f3fd",
[":superhero::skin-tone-3:"] = "\U0001f9b8\U0001f3fd",
[":superhero_tone4:"] = "\U0001f9b8\U0001f3fe",
[":superhero_medium_dark_skin_tone:"] = "\U0001f9b8\U0001f3fe",
[":superhero::skin-tone-4:"] = "\U0001f9b8\U0001f3fe",
[":superhero_tone5:"] = "\U0001f9b8\U0001f3ff",
[":superhero_dark_skin_tone:"] = "\U0001f9b8\U0001f3ff",
[":superhero::skin-tone-5:"] = "\U0001f9b8\U0001f3ff",
[":supervillain:"] = "\U0001f9b9",
[":supervillain_tone1:"] = "\U0001f9b9\U0001f3fb",
[":supervillain_light_skin_tone:"] = "\U0001f9b9\U0001f3fb",
[":supervillain::skin-tone-1:"] = "\U0001f9b9\U0001f3fb",
[":supervillain_tone2:"] = "\U0001f9b9\U0001f3fc",
[":supervillain_medium_light_skin_tone:"] = "\U0001f9b9\U0001f3fc",
[":supervillain::skin-tone-2:"] = "\U0001f9b9\U0001f3fc",
[":supervillain_tone3:"] = "\U0001f9b9\U0001f3fd",
[":supervillain_medium_skin_tone:"] = "\U0001f9b9\U0001f3fd",
[":supervillain::skin-tone-3:"] = "\U0001f9b9\U0001f3fd",
[":supervillain_tone4:"] = "\U0001f9b9\U0001f3fe",
[":supervillain_medium_dark_skin_tone:"] = "\U0001f9b9\U0001f3fe",
[":supervillain::skin-tone-4:"] = "\U0001f9b9\U0001f3fe",
[":supervillain_tone5:"] = "\U0001f9b9\U0001f3ff",
[":supervillain_dark_skin_tone:"] = "\U0001f9b9\U0001f3ff",
[":supervillain::skin-tone-5:"] = "\U0001f9b9\U0001f3ff",
[":sushi:"] = "\U0001f363",
[":suspension_railway:"] = "\U0001f69f",
[":swan:"] = "\U0001f9a2",
[":sweat:"] = "\U0001f613",
[",:("] = "\U0001f613",
[",:-("] = "\U0001f613",
[",=("] = "\U0001f613",
[",=-("] = "\U0001f613",
[":sweat_drops:"] = "\U0001f4a6",
[":sweat_smile:"] = "\U0001f605",
[",:)"] = "\U0001f605",
[",:-)"] = "\U0001f605",
[",=)"] = "\U0001f605",
[",=-)"] = "\U0001f605",
[":sweet_potato:"] = "\U0001f360",
[":symbols:"] = "\U0001f523",
[":synagogue:"] = "\U0001f54d",
[":syringe:"] = "\U0001f489",
[":t_rex:"] = "\U0001f996",
[":taco:"] = "\U0001f32e",
[":tada:"] = "\U0001f389",
[":takeout_box:"] = "\U0001f961",
[":tamale:"] = "\U0001fad4",
[":tanabata_tree:"] = "\U0001f38b",
[":tangerine:"] = "\U0001f34a",
[":taurus:"] = "\u2649",
[":taxi:"] = "\U0001f695",
[":tea:"] = "\U0001f375",
[":teacher:"] = "\U0001f9d1\u200d\U0001f3eb",
[":teacher_tone1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f3eb",
[":teacher_light_skin_tone:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f3eb",
[":teacher::skin-tone-1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f3eb",
[":teacher_tone2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f3eb",
[":teacher_medium_light_skin_tone:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f3eb",
[":teacher::skin-tone-2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f3eb",
[":teacher_tone3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f3eb",
[":teacher_medium_skin_tone:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f3eb",
[":teacher::skin-tone-3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f3eb",
[":teacher_tone4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f3eb",
[":teacher_medium_dark_skin_tone:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f3eb",
[":teacher::skin-tone-4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f3eb",
[":teacher_tone5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f3eb",
[":teacher_dark_skin_tone:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f3eb",
[":teacher::skin-tone-5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f3eb",
[":teapot:"] = "\U0001fad6",
[":technologist:"] = "\U0001f9d1\u200d\U0001f4bb",
[":technologist_tone1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f4bb",
[":technologist_light_skin_tone:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f4bb",
[":technologist::skin-tone-1:"] = "\U0001f9d1\U0001f3fb\u200d\U0001f4bb",
[":technologist_tone2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f4bb",
[":technologist_medium_light_skin_tone:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f4bb",
[":technologist::skin-tone-2:"] = "\U0001f9d1\U0001f3fc\u200d\U0001f4bb",
[":technologist_tone3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f4bb",
[":technologist_medium_skin_tone:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f4bb",
[":technologist::skin-tone-3:"] = "\U0001f9d1\U0001f3fd\u200d\U0001f4bb",
[":technologist_tone4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f4bb",
[":technologist_medium_dark_skin_tone:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f4bb",
[":technologist::skin-tone-4:"] = "\U0001f9d1\U0001f3fe\u200d\U0001f4bb",
[":technologist_tone5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f4bb",
[":technologist_dark_skin_tone:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f4bb",
[":technologist::skin-tone-5:"] = "\U0001f9d1\U0001f3ff\u200d\U0001f4bb",
[":teddy_bear:"] = "\U0001f9f8",
[":telephone:"] = "\u260e\ufe0f",
[":telephone_receiver:"] = "\U0001f4de",
[":telescope:"] = "\U0001f52d",
[":tennis:"] = "\U0001f3be",
[":tent:"] = "\u26fa",
[":test_tube:"] = "\U0001f9ea",
[":thermometer:"] = "\U0001f321\ufe0f",
[":thermometer_face:"] = "\U0001f912",
[":face_with_thermometer:"] = "\U0001f912",
[":thinking:"] = "\U0001f914",
[":thinking_face:"] = "\U0001f914",
[":third_place:"] = "\U0001f949",
[":third_place_medal:"] = "\U0001f949",
[":thong_sandal:"] = "\U0001fa74",
[":thought_balloon:"] = "\U0001f4ad",
[":thread:"] = "\U0001f9f5",
[":three:"] = "\u0033\ufe0f\u20e3",
[":thumbsdown:"] = "\U0001f44e",
[":-1:"] = "\U0001f44e",
[":thumbdown:"] = "\U0001f44e",
[":thumbsdown_tone1:"] = "\U0001f44e\U0001f3fb",
[":_1_tone1:"] = "\U0001f44e\U0001f3fb",
[":thumbdown_tone1:"] = "\U0001f44e\U0001f3fb",
[":thumbsdown::skin-tone-1:"] = "\U0001f44e\U0001f3fb",
[":-1::skin-tone-1:"] = "\U0001f44e\U0001f3fb",
[":thumbdown::skin-tone-1:"] = "\U0001f44e\U0001f3fb",
[":thumbsdown_tone2:"] = "\U0001f44e\U0001f3fc",
[":_1_tone2:"] = "\U0001f44e\U0001f3fc",
[":thumbdown_tone2:"] = "\U0001f44e\U0001f3fc",
[":thumbsdown::skin-tone-2:"] = "\U0001f44e\U0001f3fc",
[":-1::skin-tone-2:"] = "\U0001f44e\U0001f3fc",
[":thumbdown::skin-tone-2:"] = "\U0001f44e\U0001f3fc",
[":thumbsdown_tone3:"] = "\U0001f44e\U0001f3fd",
[":_1_tone3:"] = "\U0001f44e\U0001f3fd",
[":thumbdown_tone3:"] = "\U0001f44e\U0001f3fd",
[":thumbsdown::skin-tone-3:"] = "\U0001f44e\U0001f3fd",
[":-1::skin-tone-3:"] = "\U0001f44e\U0001f3fd",
[":thumbdown::skin-tone-3:"] = "\U0001f44e\U0001f3fd",
[":thumbsdown_tone4:"] = "\U0001f44e\U0001f3fe",
[":_1_tone4:"] = "\U0001f44e\U0001f3fe",
[":thumbdown_tone4:"] = "\U0001f44e\U0001f3fe",
[":thumbsdown::skin-tone-4:"] = "\U0001f44e\U0001f3fe",
[":-1::skin-tone-4:"] = "\U0001f44e\U0001f3fe",
[":thumbdown::skin-tone-4:"] = "\U0001f44e\U0001f3fe",
[":thumbsdown_tone5:"] = "\U0001f44e\U0001f3ff",
[":_1_tone5:"] = "\U0001f44e\U0001f3ff",
[":thumbdown_tone5:"] = "\U0001f44e\U0001f3ff",
[":thumbsdown::skin-tone-5:"] = "\U0001f44e\U0001f3ff",
[":-1::skin-tone-5:"] = "\U0001f44e\U0001f3ff",
[":thumbdown::skin-tone-5:"] = "\U0001f44e\U0001f3ff",
[":thumbsup:"] = "\U0001f44d",
[":+1:"] = "\U0001f44d",
[":thumbup:"] = "\U0001f44d",
[":thumbsup_tone1:"] = "\U0001f44d\U0001f3fb",
[":+1_tone1:"] = "\U0001f44d\U0001f3fb",
[":thumbup_tone1:"] = "\U0001f44d\U0001f3fb",
[":thumbsup::skin-tone-1:"] = "\U0001f44d\U0001f3fb",
[":+1::skin-tone-1:"] = "\U0001f44d\U0001f3fb",
[":thumbup::skin-tone-1:"] = "\U0001f44d\U0001f3fb",
[":thumbsup_tone2:"] = "\U0001f44d\U0001f3fc",
[":+1_tone2:"] = "\U0001f44d\U0001f3fc",
[":thumbup_tone2:"] = "\U0001f44d\U0001f3fc",
[":thumbsup::skin-tone-2:"] = "\U0001f44d\U0001f3fc",
[":+1::skin-tone-2:"] = "\U0001f44d\U0001f3fc",
[":thumbup::skin-tone-2:"] = "\U0001f44d\U0001f3fc",
[":thumbsup_tone3:"] = "\U0001f44d\U0001f3fd",
[":+1_tone3:"] = "\U0001f44d\U0001f3fd",
[":thumbup_tone3:"] = "\U0001f44d\U0001f3fd",
[":thumbsup::skin-tone-3:"] = "\U0001f44d\U0001f3fd",
[":+1::skin-tone-3:"] = "\U0001f44d\U0001f3fd",
[":thumbup::skin-tone-3:"] = "\U0001f44d\U0001f3fd",
[":thumbsup_tone4:"] = "\U0001f44d\U0001f3fe",
[":+1_tone4:"] = "\U0001f44d\U0001f3fe",
[":thumbup_tone4:"] = "\U0001f44d\U0001f3fe",
[":thumbsup::skin-tone-4:"] = "\U0001f44d\U0001f3fe",
[":+1::skin-tone-4:"] = "\U0001f44d\U0001f3fe",
[":thumbup::skin-tone-4:"] = "\U0001f44d\U0001f3fe",
[":thumbsup_tone5:"] = "\U0001f44d\U0001f3ff",
[":+1_tone5:"] = "\U0001f44d\U0001f3ff",
[":thumbup_tone5:"] = "\U0001f44d\U0001f3ff",
[":thumbsup::skin-tone-5:"] = "\U0001f44d\U0001f3ff",
[":+1::skin-tone-5:"] = "\U0001f44d\U0001f3ff",
[":thumbup::skin-tone-5:"] = "\U0001f44d\U0001f3ff",
[":thunder_cloud_rain:"] = "\u26c8\ufe0f",
[":thunder_cloud_and_rain:"] = "\u26c8\ufe0f",
[":ticket:"] = "\U0001f3ab",
[":tickets:"] = "\U0001f39f\ufe0f",
[":admission_tickets:"] = "\U0001f39f\ufe0f",
[":tiger:"] = "\U0001f42f",
[":tiger2:"] = "\U0001f405",
[":timer:"] = "\u23f2\ufe0f",
[":timer_clock:"] = "\u23f2\ufe0f",
[":tired_face:"] = "\U0001f62b",
[":tm:"] = "\u2122\ufe0f",
[":toilet:"] = "\U0001f6bd",
[":tokyo_tower:"] = "\U0001f5fc",
[":tomato:"] = "\U0001f345",
[":tongue:"] = "\U0001f445",
[":toolbox:"] = "\U0001f9f0",
[":tools:"] = "\U0001f6e0\ufe0f",
[":hammer_and_wrench:"] = "\U0001f6e0\ufe0f",
[":tooth:"] = "\U0001f9b7",
[":toothbrush:"] = "\U0001faa5",
[":top:"] = "\U0001f51d",
[":tophat:"] = "\U0001f3a9",
[":track_next:"] = "\u23ed\ufe0f",
[":next_track:"] = "\u23ed\ufe0f",
[":track_previous:"] = "\u23ee\ufe0f",
[":previous_track:"] = "\u23ee\ufe0f",
[":trackball:"] = "\U0001f5b2\ufe0f",
[":tractor:"] = "\U0001f69c",
[":traffic_light:"] = "\U0001f6a5",
[":train:"] = "\U0001f68b",
[":train2:"] = "\U0001f686",
[":tram:"] = "\U0001f68a",
[":transgender_flag:"] = "\U0001f3f3\ufe0f\u200d\u26a7\ufe0f",
[":transgender_symbol:"] = "\u26a7",
[":triangular_flag_on_post:"] = "\U0001f6a9",
[":triangular_ruler:"] = "\U0001f4d0",
[":trident:"] = "\U0001f531",
[":triumph:"] = "\U0001f624",
[":trolleybus:"] = "\U0001f68e",
[":trophy:"] = "\U0001f3c6",
[":tropical_drink:"] = "\U0001f379",
[":tropical_fish:"] = "\U0001f420",
[":truck:"] = "\U0001f69a",
[":trumpet:"] = "\U0001f3ba",
[":tulip:"] = "\U0001f337",
[":tumbler_glass:"] = "\U0001f943",
[":whisky:"] = "\U0001f943",
[":turkey:"] = "\U0001f983",
[":turtle:"] = "\U0001f422",
[":tv:"] = "\U0001f4fa",
[":twisted_rightwards_arrows:"] = "\U0001f500",
[":two:"] = "\u0032\ufe0f\u20e3",
[":two_hearts:"] = "\U0001f495",
[":two_men_holding_hands:"] = "\U0001f46c",
[":two_women_holding_hands:"] = "\U0001f46d",
[":u5272:"] = "\U0001f239",
[":u5408:"] = "\U0001f234",
[":u55b6:"] = "\U0001f23a",
[":u6307:"] = "\U0001f22f",
[":u6708:"] = "\U0001f237\ufe0f",
[":u6709:"] = "\U0001f236",
[":u6e80:"] = "\U0001f235",
[":u7121:"] = "\U0001f21a",
[":u7533:"] = "\U0001f238",
[":u7981:"] = "\U0001f232",
[":u7a7a:"] = "\U0001f233",
[":umbrella:"] = "\u2614",
[":umbrella2:"] = "\u2602\ufe0f",
[":unamused:"] = "\U0001f612",
[":s"] = "\U0001f612",
[":-S"] = "\U0001f612",
[":z"] = "\U0001f612",
[":-Z"] = "\U0001f612",
[":$"] = "\U0001f612",
[":-$"] = "\U0001f612",
["=s"] = "\U0001f612",
["=-S"] = "\U0001f612",
["=z"] = "\U0001f612",
["=-Z"] = "\U0001f612",
["=$"] = "\U0001f612",
["=-$"] = "\U0001f612",
[":underage:"] = "\U0001f51e",
[":unicorn:"] = "\U0001f984",
[":unicorn_face:"] = "\U0001f984",
[":united_nations:"] = "\U0001f1fa\U0001f1f3",
[":unlock:"] = "\U0001f513",
[":up:"] = "\U0001f199",
[":upside_down:"] = "\U0001f643",
[":upside_down_face:"] = "\U0001f643",
[":urn:"] = "\u26b1\ufe0f",
[":funeral_urn:"] = "\u26b1\ufe0f",
[":v:"] = "\u270c\ufe0f",
[":v_tone1:"] = "\u270c\U0001f3fb",
[":v::skin-tone-1:"] = "\u270c\U0001f3fb",
[":v_tone2:"] = "\u270c\U0001f3fc",
[":v::skin-tone-2:"] = "\u270c\U0001f3fc",
[":v_tone3:"] = "\u270c\U0001f3fd",
[":v::skin-tone-3:"] = "\u270c\U0001f3fd",
[":v_tone4:"] = "\u270c\U0001f3fe",
[":v::skin-tone-4:"] = "\u270c\U0001f3fe",
[":v_tone5:"] = "\u270c\U0001f3ff",
[":v::skin-tone-5:"] = "\u270c\U0001f3ff",
[":vampire:"] = "\U0001f9db",
[":vampire_tone1:"] = "\U0001f9db\U0001f3fb",
[":vampire_light_skin_tone:"] = "\U0001f9db\U0001f3fb",
[":vampire::skin-tone-1:"] = "\U0001f9db\U0001f3fb",
[":vampire_tone2:"] = "\U0001f9db\U0001f3fc",
[":vampire_medium_light_skin_tone:"] = "\U0001f9db\U0001f3fc",
[":vampire::skin-tone-2:"] = "\U0001f9db\U0001f3fc",
[":vampire_tone3:"] = "\U0001f9db\U0001f3fd",
[":vampire_medium_skin_tone:"] = "\U0001f9db\U0001f3fd",
[":vampire::skin-tone-3:"] = "\U0001f9db\U0001f3fd",
[":vampire_tone4:"] = "\U0001f9db\U0001f3fe",
[":vampire_medium_dark_skin_tone:"] = "\U0001f9db\U0001f3fe",
[":vampire::skin-tone-4:"] = "\U0001f9db\U0001f3fe",
[":vampire_tone5:"] = "\U0001f9db\U0001f3ff",
[":vampire_dark_skin_tone:"] = "\U0001f9db\U0001f3ff",
[":vampire::skin-tone-5:"] = "\U0001f9db\U0001f3ff",
[":vertical_traffic_light:"] = "\U0001f6a6",
[":vhs:"] = "\U0001f4fc",
[":vibration_mode:"] = "\U0001f4f3",
[":video_camera:"] = "\U0001f4f9",
[":video_game:"] = "\U0001f3ae",
[":violin:"] = "\U0001f3bb",
[":virgo:"] = "\u264d",
[":volcano:"] = "\U0001f30b",
[":volleyball:"] = "\U0001f3d0",
[":vs:"] = "\U0001f19a",
[":vulcan:"] = "\U0001f596",
[":raised_hand_with_part_between_middle_and_ring_fingers:"] = "\U0001f596",
[":vulcan_tone1:"] = "\U0001f596\U0001f3fb",
[":raised_hand_with_part_between_middle_and_ring_fingers_tone1:"] = "\U0001f596\U0001f3fb",
[":vulcan::skin-tone-1:"] = "\U0001f596\U0001f3fb",
[":raised_hand_with_part_between_middle_and_ring_fingers::skin-tone-1:"] = "\U0001f596\U0001f3fb",
[":vulcan_tone2:"] = "\U0001f596\U0001f3fc",
[":raised_hand_with_part_between_middle_and_ring_fingers_tone2:"] = "\U0001f596\U0001f3fc",
[":vulcan::skin-tone-2:"] = "\U0001f596\U0001f3fc",
[":raised_hand_with_part_between_middle_and_ring_fingers::skin-tone-2:"] = "\U0001f596\U0001f3fc",
[":vulcan_tone3:"] = "\U0001f596\U0001f3fd",
[":raised_hand_with_part_between_middle_and_ring_fingers_tone3:"] = "\U0001f596\U0001f3fd",
[":vulcan::skin-tone-3:"] = "\U0001f596\U0001f3fd",
[":raised_hand_with_part_between_middle_and_ring_fingers::skin-tone-3:"] = "\U0001f596\U0001f3fd",
[":vulcan_tone4:"] = "\U0001f596\U0001f3fe",
[":raised_hand_with_part_between_middle_and_ring_fingers_tone4:"] = "\U0001f596\U0001f3fe",
[":vulcan::skin-tone-4:"] = "\U0001f596\U0001f3fe",
[":raised_hand_with_part_between_middle_and_ring_fingers::skin-tone-4:"] = "\U0001f596\U0001f3fe",
[":vulcan_tone5:"] = "\U0001f596\U0001f3ff",
[":raised_hand_with_part_between_middle_and_ring_fingers_tone5:"] = "\U0001f596\U0001f3ff",
[":vulcan::skin-tone-5:"] = "\U0001f596\U0001f3ff",
[":raised_hand_with_part_between_middle_and_ring_fingers::skin-tone-5:"] = "\U0001f596\U0001f3ff",
[":waffle:"] = "\U0001f9c7",
[":wales:"] = "\U0001f3f4\U000e0067\U000e0062\U000e0077\U000e006c\U000e0073\U000e007f",
[":waning_crescent_moon:"] = "\U0001f318",
[":waning_gibbous_moon:"] = "\U0001f316",
[":warning:"] = "\u26a0\ufe0f",
[":wastebasket:"] = "\U0001f5d1\ufe0f",
[":watch:"] = "\u231a",
[":water_buffalo:"] = "\U0001f403",
[":watermelon:"] = "\U0001f349",
[":wave:"] = "\U0001f44b",
[":wave_tone1:"] = "\U0001f44b\U0001f3fb",
[":wave::skin-tone-1:"] = "\U0001f44b\U0001f3fb",
[":wave_tone2:"] = "\U0001f44b\U0001f3fc",
[":wave::skin-tone-2:"] = "\U0001f44b\U0001f3fc",
[":wave_tone3:"] = "\U0001f44b\U0001f3fd",
[":wave::skin-tone-3:"] = "\U0001f44b\U0001f3fd",
[":wave_tone4:"] = "\U0001f44b\U0001f3fe",
[":wave::skin-tone-4:"] = "\U0001f44b\U0001f3fe",
[":wave_tone5:"] = "\U0001f44b\U0001f3ff",
[":wave::skin-tone-5:"] = "\U0001f44b\U0001f3ff",
[":wavy_dash:"] = "\u3030\ufe0f",
[":waxing_crescent_moon:"] = "\U0001f312",
[":waxing_gibbous_moon:"] = "\U0001f314",
[":wc:"] = "\U0001f6be",
[":weary:"] = "\U0001f629",
[":wedding:"] = "\U0001f492",
[":whale:"] = "\U0001f433",
[":whale2:"] = "\U0001f40b",
[":wheel_of_dharma:"] = "\u2638\ufe0f",
[":wheelchair:"] = "\u267f",
[":white_check_mark:"] = "\u2705",
[":white_circle:"] = "\u26aa",
[":white_flower:"] = "\U0001f4ae",
[":white_heart:"] = "\U0001f90d",
[":white_large_square:"] = "\u2b1c",
[":white_medium_small_square:"] = "\u25fd",
[":white_medium_square:"] = "\u25fb\ufe0f",
[":white_small_square:"] = "\u25ab\ufe0f",
[":white_square_button:"] = "\U0001f533",
[":white_sun_cloud:"] = "\U0001f325\ufe0f",
[":white_sun_behind_cloud:"] = "\U0001f325\ufe0f",
[":white_sun_rain_cloud:"] = "\U0001f326\ufe0f",
[":white_sun_behind_cloud_with_rain:"] = "\U0001f326\ufe0f",
[":white_sun_small_cloud:"] = "\U0001f324\ufe0f",
[":white_sun_with_small_cloud:"] = "\U0001f324\ufe0f",
[":wilted_rose:"] = "\U0001f940",
[":wilted_flower:"] = "\U0001f940",
[":wind_blowing_face:"] = "\U0001f32c\ufe0f",
[":wind_chime:"] = "\U0001f390",
[":window:"] = "\U0001fa9f",
[":wine_glass:"] = "\U0001f377",
[":wink:"] = "\U0001f609",
[";)"] = "\U0001f609",
[";-)"] = "\U0001f609",
[":wolf:"] = "\U0001f43a",
[":woman:"] = "\U0001f469",
[":woman_and_man_holding_hands_tone1:"] = "\U0001f46b",
[":woman_and_man_holding_hands_light_skin_tone:"] = "\U0001f46b",
[":woman_and_man_holding_hands_tone1_tone2:"] = "\U0001f46b",
[":woman_and_man_holding_hands_light_skin_tone_medium_light_skin_tone:"] = "\U0001f46b",
[":woman_and_man_holding_hands_tone1_tone3:"] = "\U0001f46b",
[":woman_and_man_holding_hands_light_skin_tone_medium_skin_tone:"] = "\U0001f46b",
[":woman_and_man_holding_hands_tone1_tone4:"] = "\U0001f46b",
[":woman_and_man_holding_hands_light_skin_tone_medium_dark_skin_tone:"] = "\U0001f46b",
[":woman_and_man_holding_hands_tone1_tone5:"] = "\U0001f46b",
[":woman_and_man_holding_hands_light_skin_tone_dark_skin_tone:"] = "\U0001f46b",
[":woman_and_man_holding_hands_tone2:"] = "\U0001f46b",
[":woman_and_man_holding_hands_medium_light_skin_tone:"] = "\U0001f46b",
[":woman_and_man_holding_hands_tone2_tone1:"] = "\U0001f46b",
[":woman_and_man_holding_hands_medium_light_skin_tone_light_skin_tone:"] = "\U0001f46b",
[":woman_and_man_holding_hands_tone2_tone3:"] = "\U0001f46b",
[":woman_and_man_holding_hands_medium_light_skin_tone_medium_skin_tone:"] = "\U0001f46b",
[":woman_and_man_holding_hands_tone2_tone4:"] = "\U0001f46b",
[":woman_and_man_holding_hands_medium_light_skin_tone_medium_dark_skin_tone:"] = "\U0001f46b",
[":woman_and_man_holding_hands_tone2_tone5:"] = "\U0001f46b",
[":woman_and_man_holding_hands_medium_light_skin_tone_dark_skin_tone:"] = "\U0001f46b",
[":woman_and_man_holding_hands_tone3:"] = "\U0001f46b",
[":woman_and_man_holding_hands_medium_skin_tone:"] = "\U0001f46b",
[":woman_and_man_holding_hands_tone3_tone1:"] = "\U0001f46b",
[":woman_and_man_holding_hands_medium_skin_tone_light_skin_tone:"] = "\U0001f46b",
[":woman_and_man_holding_hands_tone3_tone2:"] = "\U0001f46b",
[":woman_and_man_holding_hands_medium_skin_tone_medium_light_skin_tone:"] = "\U0001f46b",
[":woman_and_man_holding_hands_tone3_tone4:"] = "\U0001f46b",
[":woman_and_man_holding_hands_medium_skin_tone_medium_dark_skin_tone:"] = "\U0001f46b",
[":woman_and_man_holding_hands_tone3_tone5:"] = "\U0001f46b",
[":woman_and_man_holding_hands_medium_skin_tone_dark_skin_tone:"] = "\U0001f46b",
[":woman_and_man_holding_hands_tone4:"] = "\U0001f46b",
[":woman_and_man_holding_hands_medium_dark_skin_tone:"] = "\U0001f46b",
[":woman_and_man_holding_hands_tone4_tone1:"] = "\U0001f46b",
[":woman_and_man_holding_hands_medium_dark_skin_tone_light_skin_tone:"] = "\U0001f46b",
[":woman_and_man_holding_hands_tone4_tone2:"] = "\U0001f46b",
[":woman_and_man_holding_hands_medium_dark_skin_tone_medium_light_skin_tone:"] = "\U0001f46b",
[":woman_and_man_holding_hands_tone4_tone3:"] = "\U0001f46b",
[":woman_and_man_holding_hands_medium_dark_skin_tone_medium_skin_tone:"] = "\U0001f46b",
[":woman_and_man_holding_hands_tone4_tone5:"] = "\U0001f46b",
[":woman_and_man_holding_hands_medium_dark_skin_tone_dark_skin_tone:"] = "\U0001f46b",
[":woman_and_man_holding_hands_tone5:"] = "\U0001f46b",
[":woman_and_man_holding_hands_dark_skin_tone:"] = "\U0001f46b",
[":woman_and_man_holding_hands_tone5_tone1:"] = "\U0001f46b",
[":woman_and_man_holding_hands_dark_skin_tone_light_skin_tone:"] = "\U0001f46b",
[":woman_and_man_holding_hands_tone5_tone2:"] = "\U0001f46b",
[":woman_and_man_holding_hands_dark_skin_tone_medium_light_skin_tone:"] = "\U0001f46b",
[":woman_and_man_holding_hands_tone5_tone3:"] = "\U0001f46b",
[":woman_and_man_holding_hands_dark_skin_tone_medium_skin_tone:"] = "\U0001f46b",
[":woman_and_man_holding_hands_tone5_tone4:"] = "\U0001f46b",
[":woman_and_man_holding_hands_dark_skin_tone_medium_dark_skin_tone:"] = "\U0001f46b",
[":woman_artist:"] = "\U0001f469\u200d\U0001f3a8",
[":woman_artist_tone1:"] = "\U0001f469\U0001f3fb\u200d\U0001f3a8",
[":woman_artist_light_skin_tone:"] = "\U0001f469\U0001f3fb\u200d\U0001f3a8",
[":woman_artist::skin-tone-1:"] = "\U0001f469\U0001f3fb\u200d\U0001f3a8",
[":woman_artist_tone2:"] = "\U0001f469\U0001f3fc\u200d\U0001f3a8",
[":woman_artist_medium_light_skin_tone:"] = "\U0001f469\U0001f3fc\u200d\U0001f3a8",
[":woman_artist::skin-tone-2:"] = "\U0001f469\U0001f3fc\u200d\U0001f3a8",
[":woman_artist_tone3:"] = "\U0001f469\U0001f3fd\u200d\U0001f3a8",
[":woman_artist_medium_skin_tone:"] = "\U0001f469\U0001f3fd\u200d\U0001f3a8",
[":woman_artist::skin-tone-3:"] = "\U0001f469\U0001f3fd\u200d\U0001f3a8",
[":woman_artist_tone4:"] = "\U0001f469\U0001f3fe\u200d\U0001f3a8",
[":woman_artist_medium_dark_skin_tone:"] = "\U0001f469\U0001f3fe\u200d\U0001f3a8",
[":woman_artist::skin-tone-4:"] = "\U0001f469\U0001f3fe\u200d\U0001f3a8",
[":woman_artist_tone5:"] = "\U0001f469\U0001f3ff\u200d\U0001f3a8",
[":woman_artist_dark_skin_tone:"] = "\U0001f469\U0001f3ff\u200d\U0001f3a8",
[":woman_artist::skin-tone-5:"] = "\U0001f469\U0001f3ff\u200d\U0001f3a8",
[":woman_astronaut:"] = "\U0001f469\u200d\U0001f680",
[":woman_astronaut_tone1:"] = "\U0001f469\U0001f3fb\u200d\U0001f680",
[":woman_astronaut_light_skin_tone:"] = "\U0001f469\U0001f3fb\u200d\U0001f680",
[":woman_astronaut::skin-tone-1:"] = "\U0001f469\U0001f3fb\u200d\U0001f680",
[":woman_astronaut_tone2:"] = "\U0001f469\U0001f3fc\u200d\U0001f680",
[":woman_astronaut_medium_light_skin_tone:"] = "\U0001f469\U0001f3fc\u200d\U0001f680",
[":woman_astronaut::skin-tone-2:"] = "\U0001f469\U0001f3fc\u200d\U0001f680",
[":woman_astronaut_tone3:"] = "\U0001f469\U0001f3fd\u200d\U0001f680",
[":woman_astronaut_medium_skin_tone:"] = "\U0001f469\U0001f3fd\u200d\U0001f680",
[":woman_astronaut::skin-tone-3:"] = "\U0001f469\U0001f3fd\u200d\U0001f680",
[":woman_astronaut_tone4:"] = "\U0001f469\U0001f3fe\u200d\U0001f680",
[":woman_astronaut_medium_dark_skin_tone:"] = "\U0001f469\U0001f3fe\u200d\U0001f680",
[":woman_astronaut::skin-tone-4:"] = "\U0001f469\U0001f3fe\u200d\U0001f680",
[":woman_astronaut_tone5:"] = "\U0001f469\U0001f3ff\u200d\U0001f680",
[":woman_astronaut_dark_skin_tone:"] = "\U0001f469\U0001f3ff\u200d\U0001f680",
[":woman_astronaut::skin-tone-5:"] = "\U0001f469\U0001f3ff\u200d\U0001f680",
[":woman_bald:"] = "\U0001f469\u200d\U0001f9b2",
[":woman_bald_tone1:"] = "\U0001f469\U0001f3fb\u200d\U0001f9b2",
[":woman_bald_light_skin_tone:"] = "\U0001f469\U0001f3fb\u200d\U0001f9b2",
[":woman_bald::skin-tone-1:"] = "\U0001f469\U0001f3fb\u200d\U0001f9b2",
[":woman_bald_tone2:"] = "\U0001f469\U0001f3fc\u200d\U0001f9b2",
[":woman_bald_medium_light_skin_tone:"] = "\U0001f469\U0001f3fc\u200d\U0001f9b2",
[":woman_bald::skin-tone-2:"] = "\U0001f469\U0001f3fc\u200d\U0001f9b2",
[":woman_bald_tone3:"] = "\U0001f469\U0001f3fd\u200d\U0001f9b2",
[":woman_bald_medium_skin_tone:"] = "\U0001f469\U0001f3fd\u200d\U0001f9b2",
[":woman_bald::skin-tone-3:"] = "\U0001f469\U0001f3fd\u200d\U0001f9b2",
[":woman_bald_tone4:"] = "\U0001f469\U0001f3fe\u200d\U0001f9b2",
[":woman_bald_medium_dark_skin_tone:"] = "\U0001f469\U0001f3fe\u200d\U0001f9b2",
[":woman_bald::skin-tone-4:"] = "\U0001f469\U0001f3fe\u200d\U0001f9b2",
[":woman_bald_tone5:"] = "\U0001f469\U0001f3ff\u200d\U0001f9b2",
[":woman_bald_dark_skin_tone:"] = "\U0001f469\U0001f3ff\u200d\U0001f9b2",
[":woman_bald::skin-tone-5:"] = "\U0001f469\U0001f3ff\u200d\U0001f9b2",
[":woman_biking:"] = "\U0001f6b4\u200d\u2640\ufe0f",
[":woman_biking_tone1:"] = "\U0001f6b4\U0001f3fb\u200d\u2640\ufe0f",
[":woman_biking_light_skin_tone:"] = "\U0001f6b4\U0001f3fb\u200d\u2640\ufe0f",
[":woman_biking::skin-tone-1:"] = "\U0001f6b4\U0001f3fb\u200d\u2640\ufe0f",
[":woman_biking_tone2:"] = "\U0001f6b4\U0001f3fc\u200d\u2640\ufe0f",
[":woman_biking_medium_light_skin_tone:"] = "\U0001f6b4\U0001f3fc\u200d\u2640\ufe0f",
[":woman_biking::skin-tone-2:"] = "\U0001f6b4\U0001f3fc\u200d\u2640\ufe0f",
[":woman_biking_tone3:"] = "\U0001f6b4\U0001f3fd\u200d\u2640\ufe0f",
[":woman_biking_medium_skin_tone:"] = "\U0001f6b4\U0001f3fd\u200d\u2640\ufe0f",
[":woman_biking::skin-tone-3:"] = "\U0001f6b4\U0001f3fd\u200d\u2640\ufe0f",
[":woman_biking_tone4:"] = "\U0001f6b4\U0001f3fe\u200d\u2640\ufe0f",
[":woman_biking_medium_dark_skin_tone:"] = "\U0001f6b4\U0001f3fe\u200d\u2640\ufe0f",
[":woman_biking::skin-tone-4:"] = "\U0001f6b4\U0001f3fe\u200d\u2640\ufe0f",
[":woman_biking_tone5:"] = "\U0001f6b4\U0001f3ff\u200d\u2640\ufe0f",
[":woman_biking_dark_skin_tone:"] = "\U0001f6b4\U0001f3ff\u200d\u2640\ufe0f",
[":woman_biking::skin-tone-5:"] = "\U0001f6b4\U0001f3ff\u200d\u2640\ufe0f",
[":woman_bouncing_ball:"] = "\u26f9\ufe0f\u200d\u2640\ufe0f",
[":woman_bouncing_ball_tone1:"] = "\u26f9\U0001f3fb\u200d\u2640\ufe0f",
[":woman_bouncing_ball_light_skin_tone:"] = "\u26f9\U0001f3fb\u200d\u2640\ufe0f",
[":woman_bouncing_ball::skin-tone-1:"] = "\u26f9\U0001f3fb\u200d\u2640\ufe0f",
[":woman_bouncing_ball_tone2:"] = "\u26f9\U0001f3fc\u200d\u2640\ufe0f",
[":woman_bouncing_ball_medium_light_skin_tone:"] = "\u26f9\U0001f3fc\u200d\u2640\ufe0f",
[":woman_bouncing_ball::skin-tone-2:"] = "\u26f9\U0001f3fc\u200d\u2640\ufe0f",
[":woman_bouncing_ball_tone3:"] = "\u26f9\U0001f3fd\u200d\u2640\ufe0f",
[":woman_bouncing_ball_medium_skin_tone:"] = "\u26f9\U0001f3fd\u200d\u2640\ufe0f",
[":woman_bouncing_ball::skin-tone-3:"] = "\u26f9\U0001f3fd\u200d\u2640\ufe0f",
[":woman_bouncing_ball_tone4:"] = "\u26f9\U0001f3fe\u200d\u2640\ufe0f",
[":woman_bouncing_ball_medium_dark_skin_tone:"] = "\u26f9\U0001f3fe\u200d\u2640\ufe0f",
[":woman_bouncing_ball::skin-tone-4:"] = "\u26f9\U0001f3fe\u200d\u2640\ufe0f",
[":woman_bouncing_ball_tone5:"] = "\u26f9\U0001f3ff\u200d\u2640\ufe0f",
[":woman_bouncing_ball_dark_skin_tone:"] = "\u26f9\U0001f3ff\u200d\u2640\ufe0f",
[":woman_bouncing_ball::skin-tone-5:"] = "\u26f9\U0001f3ff\u200d\u2640\ufe0f",
[":woman_bowing:"] = "\U0001f647\u200d\u2640\ufe0f",
[":woman_bowing_tone1:"] = "\U0001f647\U0001f3fb\u200d\u2640\ufe0f",
[":woman_bowing_light_skin_tone:"] = "\U0001f647\U0001f3fb\u200d\u2640\ufe0f",
[":woman_bowing::skin-tone-1:"] = "\U0001f647\U0001f3fb\u200d\u2640\ufe0f",
[":woman_bowing_tone2:"] = "\U0001f647\U0001f3fc\u200d\u2640\ufe0f",
[":woman_bowing_medium_light_skin_tone:"] = "\U0001f647\U0001f3fc\u200d\u2640\ufe0f",
[":woman_bowing::skin-tone-2:"] = "\U0001f647\U0001f3fc\u200d\u2640\ufe0f",
[":woman_bowing_tone3:"] = "\U0001f647\U0001f3fd\u200d\u2640\ufe0f",
[":woman_bowing_medium_skin_tone:"] = "\U0001f647\U0001f3fd\u200d\u2640\ufe0f",
[":woman_bowing::skin-tone-3:"] = "\U0001f647\U0001f3fd\u200d\u2640\ufe0f",
[":woman_bowing_tone4:"] = "\U0001f647\U0001f3fe\u200d\u2640\ufe0f",
[":woman_bowing_medium_dark_skin_tone:"] = "\U0001f647\U0001f3fe\u200d\u2640\ufe0f",
[":woman_bowing::skin-tone-4:"] = "\U0001f647\U0001f3fe\u200d\u2640\ufe0f",
[":woman_bowing_tone5:"] = "\U0001f647\U0001f3ff\u200d\u2640\ufe0f",
[":woman_bowing_dark_skin_tone:"] = "\U0001f647\U0001f3ff\u200d\u2640\ufe0f",
[":woman_bowing::skin-tone-5:"] = "\U0001f647\U0001f3ff\u200d\u2640\ufe0f",
[":woman_cartwheeling:"] = "\U0001f938\u200d\u2640\ufe0f",
[":woman_cartwheeling_tone1:"] = "\U0001f938\U0001f3fb\u200d\u2640\ufe0f",
[":woman_cartwheeling_light_skin_tone:"] = "\U0001f938\U0001f3fb\u200d\u2640\ufe0f",
[":woman_cartwheeling::skin-tone-1:"] = "\U0001f938\U0001f3fb\u200d\u2640\ufe0f",
[":woman_cartwheeling_tone2:"] = "\U0001f938\U0001f3fc\u200d\u2640\ufe0f",
[":woman_cartwheeling_medium_light_skin_tone:"] = "\U0001f938\U0001f3fc\u200d\u2640\ufe0f",
[":woman_cartwheeling::skin-tone-2:"] = "\U0001f938\U0001f3fc\u200d\u2640\ufe0f",
[":woman_cartwheeling_tone3:"] = "\U0001f938\U0001f3fd\u200d\u2640\ufe0f",
[":woman_cartwheeling_medium_skin_tone:"] = "\U0001f938\U0001f3fd\u200d\u2640\ufe0f",
[":woman_cartwheeling::skin-tone-3:"] = "\U0001f938\U0001f3fd\u200d\u2640\ufe0f",
[":woman_cartwheeling_tone4:"] = "\U0001f938\U0001f3fe\u200d\u2640\ufe0f",
[":woman_cartwheeling_medium_dark_skin_tone:"] = "\U0001f938\U0001f3fe\u200d\u2640\ufe0f",
[":woman_cartwheeling::skin-tone-4:"] = "\U0001f938\U0001f3fe\u200d\u2640\ufe0f",
[":woman_cartwheeling_tone5:"] = "\U0001f938\U0001f3ff\u200d\u2640\ufe0f",
[":woman_cartwheeling_dark_skin_tone:"] = "\U0001f938\U0001f3ff\u200d\u2640\ufe0f",
[":woman_cartwheeling::skin-tone-5:"] = "\U0001f938\U0001f3ff\u200d\u2640\ufe0f",
[":woman_climbing:"] = "\U0001f9d7\u200d\u2640\ufe0f",
[":woman_climbing_tone1:"] = "\U0001f9d7\U0001f3fb\u200d\u2640\ufe0f",
[":woman_climbing_light_skin_tone:"] = "\U0001f9d7\U0001f3fb\u200d\u2640\ufe0f",
[":woman_climbing::skin-tone-1:"] = "\U0001f9d7\U0001f3fb\u200d\u2640\ufe0f",
[":woman_climbing_tone2:"] = "\U0001f9d7\U0001f3fc\u200d\u2640\ufe0f",
[":woman_climbing_medium_light_skin_tone:"] = "\U0001f9d7\U0001f3fc\u200d\u2640\ufe0f",
[":woman_climbing::skin-tone-2:"] = "\U0001f9d7\U0001f3fc\u200d\u2640\ufe0f",
[":woman_climbing_tone3:"] = "\U0001f9d7\U0001f3fd\u200d\u2640\ufe0f",
[":woman_climbing_medium_skin_tone:"] = "\U0001f9d7\U0001f3fd\u200d\u2640\ufe0f",
[":woman_climbing::skin-tone-3:"] = "\U0001f9d7\U0001f3fd\u200d\u2640\ufe0f",
[":woman_climbing_tone4:"] = "\U0001f9d7\U0001f3fe\u200d\u2640\ufe0f",
[":woman_climbing_medium_dark_skin_tone:"] = "\U0001f9d7\U0001f3fe\u200d\u2640\ufe0f",
[":woman_climbing::skin-tone-4:"] = "\U0001f9d7\U0001f3fe\u200d\u2640\ufe0f",
[":woman_climbing_tone5:"] = "\U0001f9d7\U0001f3ff\u200d\u2640\ufe0f",
[":woman_climbing_dark_skin_tone:"] = "\U0001f9d7\U0001f3ff\u200d\u2640\ufe0f",
[":woman_climbing::skin-tone-5:"] = "\U0001f9d7\U0001f3ff\u200d\u2640\ufe0f",
[":woman_construction_worker:"] = "\U0001f477\u200d\u2640\ufe0f",
[":woman_construction_worker_tone1:"] = "\U0001f477\U0001f3fb\u200d\u2640\ufe0f",
[":woman_construction_worker_light_skin_tone:"] = "\U0001f477\U0001f3fb\u200d\u2640\ufe0f",
[":woman_construction_worker::skin-tone-1:"] = "\U0001f477\U0001f3fb\u200d\u2640\ufe0f",
[":woman_construction_worker_tone2:"] = "\U0001f477\U0001f3fc\u200d\u2640\ufe0f",
[":woman_construction_worker_medium_light_skin_tone:"] = "\U0001f477\U0001f3fc\u200d\u2640\ufe0f",
[":woman_construction_worker::skin-tone-2:"] = "\U0001f477\U0001f3fc\u200d\u2640\ufe0f",
[":woman_construction_worker_tone3:"] = "\U0001f477\U0001f3fd\u200d\u2640\ufe0f",
[":woman_construction_worker_medium_skin_tone:"] = "\U0001f477\U0001f3fd\u200d\u2640\ufe0f",
[":woman_construction_worker::skin-tone-3:"] = "\U0001f477\U0001f3fd\u200d\u2640\ufe0f",
[":woman_construction_worker_tone4:"] = "\U0001f477\U0001f3fe\u200d\u2640\ufe0f",
[":woman_construction_worker_medium_dark_skin_tone:"] = "\U0001f477\U0001f3fe\u200d\u2640\ufe0f",
[":woman_construction_worker::skin-tone-4:"] = "\U0001f477\U0001f3fe\u200d\u2640\ufe0f",
[":woman_construction_worker_tone5:"] = "\U0001f477\U0001f3ff\u200d\u2640\ufe0f",
[":woman_construction_worker_dark_skin_tone:"] = "\U0001f477\U0001f3ff\u200d\u2640\ufe0f",
[":woman_construction_worker::skin-tone-5:"] = "\U0001f477\U0001f3ff\u200d\u2640\ufe0f",
[":woman_cook:"] = "\U0001f469\u200d\U0001f373",
[":woman_cook_tone1:"] = "\U0001f469\U0001f3fb\u200d\U0001f373",
[":woman_cook_light_skin_tone:"] = "\U0001f469\U0001f3fb\u200d\U0001f373",
[":woman_cook::skin-tone-1:"] = "\U0001f469\U0001f3fb\u200d\U0001f373",
[":woman_cook_tone2:"] = "\U0001f469\U0001f3fc\u200d\U0001f373",
[":woman_cook_medium_light_skin_tone:"] = "\U0001f469\U0001f3fc\u200d\U0001f373",
[":woman_cook::skin-tone-2:"] = "\U0001f469\U0001f3fc\u200d\U0001f373",
[":woman_cook_tone3:"] = "\U0001f469\U0001f3fd\u200d\U0001f373",
[":woman_cook_medium_skin_tone:"] = "\U0001f469\U0001f3fd\u200d\U0001f373",
[":woman_cook::skin-tone-3:"] = "\U0001f469\U0001f3fd\u200d\U0001f373",
[":woman_cook_tone4:"] = "\U0001f469\U0001f3fe\u200d\U0001f373",
[":woman_cook_medium_dark_skin_tone:"] = "\U0001f469\U0001f3fe\u200d\U0001f373",
[":woman_cook::skin-tone-4:"] = "\U0001f469\U0001f3fe\u200d\U0001f373",
[":woman_cook_tone5:"] = "\U0001f469\U0001f3ff\u200d\U0001f373",
[":woman_cook_dark_skin_tone:"] = "\U0001f469\U0001f3ff\u200d\U0001f373",
[":woman_cook::skin-tone-5:"] = "\U0001f469\U0001f3ff\u200d\U0001f373",
[":woman_curly_haired:"] = "\U0001f469\u200d\U0001f9b1",
[":woman_curly_haired_tone1:"] = "\U0001f469\U0001f3fb\u200d\U0001f9b1",
[":woman_curly_haired_light_skin_tone:"] = "\U0001f469\U0001f3fb\u200d\U0001f9b1",
[":woman_curly_haired::skin-tone-1:"] = "\U0001f469\U0001f3fb\u200d\U0001f9b1",
[":woman_curly_haired_tone2:"] = "\U0001f469\U0001f3fc\u200d\U0001f9b1",
[":woman_curly_haired_medium_light_skin_tone:"] = "\U0001f469\U0001f3fc\u200d\U0001f9b1",
[":woman_curly_haired::skin-tone-2:"] = "\U0001f469\U0001f3fc\u200d\U0001f9b1",
[":woman_curly_haired_tone3:"] = "\U0001f469\U0001f3fd\u200d\U0001f9b1",
[":woman_curly_haired_medium_skin_tone:"] = "\U0001f469\U0001f3fd\u200d\U0001f9b1",
[":woman_curly_haired::skin-tone-3:"] = "\U0001f469\U0001f3fd\u200d\U0001f9b1",
[":woman_curly_haired_tone4:"] = "\U0001f469\U0001f3fe\u200d\U0001f9b1",
[":woman_curly_haired_medium_dark_skin_tone:"] = "\U0001f469\U0001f3fe\u200d\U0001f9b1",
[":woman_curly_haired::skin-tone-4:"] = "\U0001f469\U0001f3fe\u200d\U0001f9b1",
[":woman_curly_haired_tone5:"] = "\U0001f469\U0001f3ff\u200d\U0001f9b1",
[":woman_curly_haired_dark_skin_tone:"] = "\U0001f469\U0001f3ff\u200d\U0001f9b1",
[":woman_curly_haired::skin-tone-5:"] = "\U0001f469\U0001f3ff\u200d\U0001f9b1",
[":woman_detective:"] = "\U0001f575\ufe0f\u200d\u2640\ufe0f",
[":woman_detective_tone1:"] = "\U0001f575\U0001f3fb\u200d\u2640\ufe0f",
[":woman_detective_light_skin_tone:"] = "\U0001f575\U0001f3fb\u200d\u2640\ufe0f",
[":woman_detective::skin-tone-1:"] = "\U0001f575\U0001f3fb\u200d\u2640\ufe0f",
[":woman_detective_tone2:"] = "\U0001f575\U0001f3fc\u200d\u2640\ufe0f",
[":woman_detective_medium_light_skin_tone:"] = "\U0001f575\U0001f3fc\u200d\u2640\ufe0f",
[":woman_detective::skin-tone-2:"] = "\U0001f575\U0001f3fc\u200d\u2640\ufe0f",
[":woman_detective_tone3:"] = "\U0001f575\U0001f3fd\u200d\u2640\ufe0f",
[":woman_detective_medium_skin_tone:"] = "\U0001f575\U0001f3fd\u200d\u2640\ufe0f",
[":woman_detective::skin-tone-3:"] = "\U0001f575\U0001f3fd\u200d\u2640\ufe0f",
[":woman_detective_tone4:"] = "\U0001f575\U0001f3fe\u200d\u2640\ufe0f",
[":woman_detective_medium_dark_skin_tone:"] = "\U0001f575\U0001f3fe\u200d\u2640\ufe0f",
[":woman_detective::skin-tone-4:"] = "\U0001f575\U0001f3fe\u200d\u2640\ufe0f",
[":woman_detective_tone5:"] = "\U0001f575\U0001f3ff\u200d\u2640\ufe0f",
[":woman_detective_dark_skin_tone:"] = "\U0001f575\U0001f3ff\u200d\u2640\ufe0f",
[":woman_detective::skin-tone-5:"] = "\U0001f575\U0001f3ff\u200d\u2640\ufe0f",
[":woman_elf:"] = "\U0001f9dd\u200d\u2640\ufe0f",
[":woman_elf_tone1:"] = "\U0001f9dd\U0001f3fb\u200d\u2640\ufe0f",
[":woman_elf_light_skin_tone:"] = "\U0001f9dd\U0001f3fb\u200d\u2640\ufe0f",
[":woman_elf::skin-tone-1:"] = "\U0001f9dd\U0001f3fb\u200d\u2640\ufe0f",
[":woman_elf_tone2:"] = "\U0001f9dd\U0001f3fc\u200d\u2640\ufe0f",
[":woman_elf_medium_light_skin_tone:"] = "\U0001f9dd\U0001f3fc\u200d\u2640\ufe0f",
[":woman_elf::skin-tone-2:"] = "\U0001f9dd\U0001f3fc\u200d\u2640\ufe0f",
[":woman_elf_tone3:"] = "\U0001f9dd\U0001f3fd\u200d\u2640\ufe0f",
[":woman_elf_medium_skin_tone:"] = "\U0001f9dd\U0001f3fd\u200d\u2640\ufe0f",
[":woman_elf::skin-tone-3:"] = "\U0001f9dd\U0001f3fd\u200d\u2640\ufe0f",
[":woman_elf_tone4:"] = "\U0001f9dd\U0001f3fe\u200d\u2640\ufe0f",
[":woman_elf_medium_dark_skin_tone:"] = "\U0001f9dd\U0001f3fe\u200d\u2640\ufe0f",
[":woman_elf::skin-tone-4:"] = "\U0001f9dd\U0001f3fe\u200d\u2640\ufe0f",
[":woman_elf_tone5:"] = "\U0001f9dd\U0001f3ff\u200d\u2640\ufe0f",
[":woman_elf_dark_skin_tone:"] = "\U0001f9dd\U0001f3ff\u200d\u2640\ufe0f",
[":woman_elf::skin-tone-5:"] = "\U0001f9dd\U0001f3ff\u200d\u2640\ufe0f",
[":woman_facepalming:"] = "\U0001f926\u200d\u2640\ufe0f",
[":woman_facepalming_tone1:"] = "\U0001f926\U0001f3fb\u200d\u2640\ufe0f",
[":woman_facepalming_light_skin_tone:"] = "\U0001f926\U0001f3fb\u200d\u2640\ufe0f",
[":woman_facepalming::skin-tone-1:"] = "\U0001f926\U0001f3fb\u200d\u2640\ufe0f",
[":woman_facepalming_tone2:"] = "\U0001f926\U0001f3fc\u200d\u2640\ufe0f",
[":woman_facepalming_medium_light_skin_tone:"] = "\U0001f926\U0001f3fc\u200d\u2640\ufe0f",
[":woman_facepalming::skin-tone-2:"] = "\U0001f926\U0001f3fc\u200d\u2640\ufe0f",
[":woman_facepalming_tone3:"] = "\U0001f926\U0001f3fd\u200d\u2640\ufe0f",
[":woman_facepalming_medium_skin_tone:"] = "\U0001f926\U0001f3fd\u200d\u2640\ufe0f",
[":woman_facepalming::skin-tone-3:"] = "\U0001f926\U0001f3fd\u200d\u2640\ufe0f",
[":woman_facepalming_tone4:"] = "\U0001f926\U0001f3fe\u200d\u2640\ufe0f",
[":woman_facepalming_medium_dark_skin_tone:"] = "\U0001f926\U0001f3fe\u200d\u2640\ufe0f",
[":woman_facepalming::skin-tone-4:"] = "\U0001f926\U0001f3fe\u200d\u2640\ufe0f",
[":woman_facepalming_tone5:"] = "\U0001f926\U0001f3ff\u200d\u2640\ufe0f",
[":woman_facepalming_dark_skin_tone:"] = "\U0001f926\U0001f3ff\u200d\u2640\ufe0f",
[":woman_facepalming::skin-tone-5:"] = "\U0001f926\U0001f3ff\u200d\u2640\ufe0f",
[":woman_factory_worker:"] = "\U0001f469\u200d\U0001f3ed",
[":woman_factory_worker_tone1:"] = "\U0001f469\U0001f3fb\u200d\U0001f3ed",
[":woman_factory_worker_light_skin_tone:"] = "\U0001f469\U0001f3fb\u200d\U0001f3ed",
[":woman_factory_worker::skin-tone-1:"] = "\U0001f469\U0001f3fb\u200d\U0001f3ed",
[":woman_factory_worker_tone2:"] = "\U0001f469\U0001f3fc\u200d\U0001f3ed",
[":woman_factory_worker_medium_light_skin_tone:"] = "\U0001f469\U0001f3fc\u200d\U0001f3ed",
[":woman_factory_worker::skin-tone-2:"] = "\U0001f469\U0001f3fc\u200d\U0001f3ed",
[":woman_factory_worker_tone3:"] = "\U0001f469\U0001f3fd\u200d\U0001f3ed",
[":woman_factory_worker_medium_skin_tone:"] = "\U0001f469\U0001f3fd\u200d\U0001f3ed",
[":woman_factory_worker::skin-tone-3:"] = "\U0001f469\U0001f3fd\u200d\U0001f3ed",
[":woman_factory_worker_tone4:"] = "\U0001f469\U0001f3fe\u200d\U0001f3ed",
[":woman_factory_worker_medium_dark_skin_tone:"] = "\U0001f469\U0001f3fe\u200d\U0001f3ed",
[":woman_factory_worker::skin-tone-4:"] = "\U0001f469\U0001f3fe\u200d\U0001f3ed",
[":woman_factory_worker_tone5:"] = "\U0001f469\U0001f3ff\u200d\U0001f3ed",
[":woman_factory_worker_dark_skin_tone:"] = "\U0001f469\U0001f3ff\u200d\U0001f3ed",
[":woman_factory_worker::skin-tone-5:"] = "\U0001f469\U0001f3ff\u200d\U0001f3ed",
[":woman_fairy:"] = "\U0001f9da\u200d\u2640\ufe0f",
[":woman_fairy_tone1:"] = "\U0001f9da\U0001f3fb\u200d\u2640\ufe0f",
[":woman_fairy_light_skin_tone:"] = "\U0001f9da\U0001f3fb\u200d\u2640\ufe0f",
[":woman_fairy::skin-tone-1:"] = "\U0001f9da\U0001f3fb\u200d\u2640\ufe0f",
[":woman_fairy_tone2:"] = "\U0001f9da\U0001f3fc\u200d\u2640\ufe0f",
[":woman_fairy_medium_light_skin_tone:"] = "\U0001f9da\U0001f3fc\u200d\u2640\ufe0f",
[":woman_fairy::skin-tone-2:"] = "\U0001f9da\U0001f3fc\u200d\u2640\ufe0f",
[":woman_fairy_tone3:"] = "\U0001f9da\U0001f3fd\u200d\u2640\ufe0f",
[":woman_fairy_medium_skin_tone:"] = "\U0001f9da\U0001f3fd\u200d\u2640\ufe0f",
[":woman_fairy::skin-tone-3:"] = "\U0001f9da\U0001f3fd\u200d\u2640\ufe0f",
[":woman_fairy_tone4:"] = "\U0001f9da\U0001f3fe\u200d\u2640\ufe0f",
[":woman_fairy_medium_dark_skin_tone:"] = "\U0001f9da\U0001f3fe\u200d\u2640\ufe0f",
[":woman_fairy::skin-tone-4:"] = "\U0001f9da\U0001f3fe\u200d\u2640\ufe0f",
[":woman_fairy_tone5:"] = "\U0001f9da\U0001f3ff\u200d\u2640\ufe0f",
[":woman_fairy_dark_skin_tone:"] = "\U0001f9da\U0001f3ff\u200d\u2640\ufe0f",
[":woman_fairy::skin-tone-5:"] = "\U0001f9da\U0001f3ff\u200d\u2640\ufe0f",
[":woman_farmer:"] = "\U0001f469\u200d\U0001f33e",
[":woman_farmer_tone1:"] = "\U0001f469\U0001f3fb\u200d\U0001f33e",
[":woman_farmer_light_skin_tone:"] = "\U0001f469\U0001f3fb\u200d\U0001f33e",
[":woman_farmer::skin-tone-1:"] = "\U0001f469\U0001f3fb\u200d\U0001f33e",
[":woman_farmer_tone2:"] = "\U0001f469\U0001f3fc\u200d\U0001f33e",
[":woman_farmer_medium_light_skin_tone:"] = "\U0001f469\U0001f3fc\u200d\U0001f33e",
[":woman_farmer::skin-tone-2:"] = "\U0001f469\U0001f3fc\u200d\U0001f33e",
[":woman_farmer_tone3:"] = "\U0001f469\U0001f3fd\u200d\U0001f33e",
[":woman_farmer_medium_skin_tone:"] = "\U0001f469\U0001f3fd\u200d\U0001f33e",
[":woman_farmer::skin-tone-3:"] = "\U0001f469\U0001f3fd\u200d\U0001f33e",
[":woman_farmer_tone4:"] = "\U0001f469\U0001f3fe\u200d\U0001f33e",
[":woman_farmer_medium_dark_skin_tone:"] = "\U0001f469\U0001f3fe\u200d\U0001f33e",
[":woman_farmer::skin-tone-4:"] = "\U0001f469\U0001f3fe\u200d\U0001f33e",
[":woman_farmer_tone5:"] = "\U0001f469\U0001f3ff\u200d\U0001f33e",
[":woman_farmer_dark_skin_tone:"] = "\U0001f469\U0001f3ff\u200d\U0001f33e",
[":woman_farmer::skin-tone-5:"] = "\U0001f469\U0001f3ff\u200d\U0001f33e",
[":woman_feeding_baby:"] = "\U0001f469\u200d\U0001f37c",
[":woman_feeding_baby_tone1:"] = "\U0001f469\U0001f3fb\u200d\U0001f37c",
[":woman_feeding_baby_light_skin_tone:"] = "\U0001f469\U0001f3fb\u200d\U0001f37c",
[":woman_feeding_baby::skin-tone-1:"] = "\U0001f469\U0001f3fb\u200d\U0001f37c",
[":woman_feeding_baby_tone2:"] = "\U0001f469\U0001f3fc\u200d\U0001f37c",
[":woman_feeding_baby_medium_light_skin_tone:"] = "\U0001f469\U0001f3fc\u200d\U0001f37c",
[":woman_feeding_baby::skin-tone-2:"] = "\U0001f469\U0001f3fc\u200d\U0001f37c",
[":woman_feeding_baby_tone3:"] = "\U0001f469\U0001f3fd\u200d\U0001f37c",
[":woman_feeding_baby_medium_skin_tone:"] = "\U0001f469\U0001f3fd\u200d\U0001f37c",
[":woman_feeding_baby::skin-tone-3:"] = "\U0001f469\U0001f3fd\u200d\U0001f37c",
[":woman_feeding_baby_tone4:"] = "\U0001f469\U0001f3fe\u200d\U0001f37c",
[":woman_feeding_baby_medium_dark_skin_tone:"] = "\U0001f469\U0001f3fe\u200d\U0001f37c",
[":woman_feeding_baby::skin-tone-4:"] = "\U0001f469\U0001f3fe\u200d\U0001f37c",
[":woman_feeding_baby_tone5:"] = "\U0001f469\U0001f3ff\u200d\U0001f37c",
[":woman_feeding_baby_dark_skin_tone:"] = "\U0001f469\U0001f3ff\u200d\U0001f37c",
[":woman_feeding_baby::skin-tone-5:"] = "\U0001f469\U0001f3ff\u200d\U0001f37c",
[":woman_firefighter:"] = "\U0001f469\u200d\U0001f692",
[":woman_firefighter_tone1:"] = "\U0001f469\U0001f3fb\u200d\U0001f692",
[":woman_firefighter_light_skin_tone:"] = "\U0001f469\U0001f3fb\u200d\U0001f692",
[":woman_firefighter::skin-tone-1:"] = "\U0001f469\U0001f3fb\u200d\U0001f692",
[":woman_firefighter_tone2:"] = "\U0001f469\U0001f3fc\u200d\U0001f692",
[":woman_firefighter_medium_light_skin_tone:"] = "\U0001f469\U0001f3fc\u200d\U0001f692",
[":woman_firefighter::skin-tone-2:"] = "\U0001f469\U0001f3fc\u200d\U0001f692",
[":woman_firefighter_tone3:"] = "\U0001f469\U0001f3fd\u200d\U0001f692",
[":woman_firefighter_medium_skin_tone:"] = "\U0001f469\U0001f3fd\u200d\U0001f692",
[":woman_firefighter::skin-tone-3:"] = "\U0001f469\U0001f3fd\u200d\U0001f692",
[":woman_firefighter_tone4:"] = "\U0001f469\U0001f3fe\u200d\U0001f692",
[":woman_firefighter_medium_dark_skin_tone:"] = "\U0001f469\U0001f3fe\u200d\U0001f692",
[":woman_firefighter::skin-tone-4:"] = "\U0001f469\U0001f3fe\u200d\U0001f692",
[":woman_firefighter_tone5:"] = "\U0001f469\U0001f3ff\u200d\U0001f692",
[":woman_firefighter_dark_skin_tone:"] = "\U0001f469\U0001f3ff\u200d\U0001f692",
[":woman_firefighter::skin-tone-5:"] = "\U0001f469\U0001f3ff\u200d\U0001f692",
[":woman_frowning:"] = "\U0001f64d\u200d\u2640\ufe0f",
[":woman_frowning_tone1:"] = "\U0001f64d\U0001f3fb\u200d\u2640\ufe0f",
[":woman_frowning_light_skin_tone:"] = "\U0001f64d\U0001f3fb\u200d\u2640\ufe0f",
[":woman_frowning::skin-tone-1:"] = "\U0001f64d\U0001f3fb\u200d\u2640\ufe0f",
[":woman_frowning_tone2:"] = "\U0001f64d\U0001f3fc\u200d\u2640\ufe0f",
[":woman_frowning_medium_light_skin_tone:"] = "\U0001f64d\U0001f3fc\u200d\u2640\ufe0f",
[":woman_frowning::skin-tone-2:"] = "\U0001f64d\U0001f3fc\u200d\u2640\ufe0f",
[":woman_frowning_tone3:"] = "\U0001f64d\U0001f3fd\u200d\u2640\ufe0f",
[":woman_frowning_medium_skin_tone:"] = "\U0001f64d\U0001f3fd\u200d\u2640\ufe0f",
[":woman_frowning::skin-tone-3:"] = "\U0001f64d\U0001f3fd\u200d\u2640\ufe0f",
[":woman_frowning_tone4:"] = "\U0001f64d\U0001f3fe\u200d\u2640\ufe0f",
[":woman_frowning_medium_dark_skin_tone:"] = "\U0001f64d\U0001f3fe\u200d\u2640\ufe0f",
[":woman_frowning::skin-tone-4:"] = "\U0001f64d\U0001f3fe\u200d\u2640\ufe0f",
[":woman_frowning_tone5:"] = "\U0001f64d\U0001f3ff\u200d\u2640\ufe0f",
[":woman_frowning_dark_skin_tone:"] = "\U0001f64d\U0001f3ff\u200d\u2640\ufe0f",
[":woman_frowning::skin-tone-5:"] = "\U0001f64d\U0001f3ff\u200d\u2640\ufe0f",
[":woman_genie:"] = "\U0001f9de\u200d\u2640\ufe0f",
[":woman_gesturing_no:"] = "\U0001f645\u200d\u2640\ufe0f",
[":woman_gesturing_no_tone1:"] = "\U0001f645\U0001f3fb\u200d\u2640\ufe0f",
[":woman_gesturing_no_light_skin_tone:"] = "\U0001f645\U0001f3fb\u200d\u2640\ufe0f",
[":woman_gesturing_no::skin-tone-1:"] = "\U0001f645\U0001f3fb\u200d\u2640\ufe0f",
[":woman_gesturing_no_tone2:"] = "\U0001f645\U0001f3fc\u200d\u2640\ufe0f",
[":woman_gesturing_no_medium_light_skin_tone:"] = "\U0001f645\U0001f3fc\u200d\u2640\ufe0f",
[":woman_gesturing_no::skin-tone-2:"] = "\U0001f645\U0001f3fc\u200d\u2640\ufe0f",
[":woman_gesturing_no_tone3:"] = "\U0001f645\U0001f3fd\u200d\u2640\ufe0f",
[":woman_gesturing_no_medium_skin_tone:"] = "\U0001f645\U0001f3fd\u200d\u2640\ufe0f",
[":woman_gesturing_no::skin-tone-3:"] = "\U0001f645\U0001f3fd\u200d\u2640\ufe0f",
[":woman_gesturing_no_tone4:"] = "\U0001f645\U0001f3fe\u200d\u2640\ufe0f",
[":woman_gesturing_no_medium_dark_skin_tone:"] = "\U0001f645\U0001f3fe\u200d\u2640\ufe0f",
[":woman_gesturing_no::skin-tone-4:"] = "\U0001f645\U0001f3fe\u200d\u2640\ufe0f",
[":woman_gesturing_no_tone5:"] = "\U0001f645\U0001f3ff\u200d\u2640\ufe0f",
[":woman_gesturing_no_dark_skin_tone:"] = "\U0001f645\U0001f3ff\u200d\u2640\ufe0f",
[":woman_gesturing_no::skin-tone-5:"] = "\U0001f645\U0001f3ff\u200d\u2640\ufe0f",
[":woman_gesturing_ok:"] = "\U0001f646\u200d\u2640\ufe0f",
[":woman_gesturing_ok_tone1:"] = "\U0001f646\U0001f3fb\u200d\u2640\ufe0f",
[":woman_gesturing_ok_light_skin_tone:"] = "\U0001f646\U0001f3fb\u200d\u2640\ufe0f",
[":woman_gesturing_ok::skin-tone-1:"] = "\U0001f646\U0001f3fb\u200d\u2640\ufe0f",
[":woman_gesturing_ok_tone2:"] = "\U0001f646\U0001f3fc\u200d\u2640\ufe0f",
[":woman_gesturing_ok_medium_light_skin_tone:"] = "\U0001f646\U0001f3fc\u200d\u2640\ufe0f",
[":woman_gesturing_ok::skin-tone-2:"] = "\U0001f646\U0001f3fc\u200d\u2640\ufe0f",
[":woman_gesturing_ok_tone3:"] = "\U0001f646\U0001f3fd\u200d\u2640\ufe0f",
[":woman_gesturing_ok_medium_skin_tone:"] = "\U0001f646\U0001f3fd\u200d\u2640\ufe0f",
[":woman_gesturing_ok::skin-tone-3:"] = "\U0001f646\U0001f3fd\u200d\u2640\ufe0f",
[":woman_gesturing_ok_tone4:"] = "\U0001f646\U0001f3fe\u200d\u2640\ufe0f",
[":woman_gesturing_ok_medium_dark_skin_tone:"] = "\U0001f646\U0001f3fe\u200d\u2640\ufe0f",
[":woman_gesturing_ok::skin-tone-4:"] = "\U0001f646\U0001f3fe\u200d\u2640\ufe0f",
[":woman_gesturing_ok_tone5:"] = "\U0001f646\U0001f3ff\u200d\u2640\ufe0f",
[":woman_gesturing_ok_dark_skin_tone:"] = "\U0001f646\U0001f3ff\u200d\u2640\ufe0f",
[":woman_gesturing_ok::skin-tone-5:"] = "\U0001f646\U0001f3ff\u200d\u2640\ufe0f",
[":woman_getting_face_massage:"] = "\U0001f486\u200d\u2640\ufe0f",
[":woman_getting_face_massage_tone1:"] = "\U0001f486\U0001f3fb\u200d\u2640\ufe0f",
[":woman_getting_face_massage_light_skin_tone:"] = "\U0001f486\U0001f3fb\u200d\u2640\ufe0f",
[":woman_getting_face_massage::skin-tone-1:"] = "\U0001f486\U0001f3fb\u200d\u2640\ufe0f",
[":woman_getting_face_massage_tone2:"] = "\U0001f486\U0001f3fc\u200d\u2640\ufe0f",
[":woman_getting_face_massage_medium_light_skin_tone:"] = "\U0001f486\U0001f3fc\u200d\u2640\ufe0f",
[":woman_getting_face_massage::skin-tone-2:"] = "\U0001f486\U0001f3fc\u200d\u2640\ufe0f",
[":woman_getting_face_massage_tone3:"] = "\U0001f486\U0001f3fd\u200d\u2640\ufe0f",
[":woman_getting_face_massage_medium_skin_tone:"] = "\U0001f486\U0001f3fd\u200d\u2640\ufe0f",
[":woman_getting_face_massage::skin-tone-3:"] = "\U0001f486\U0001f3fd\u200d\u2640\ufe0f",
[":woman_getting_face_massage_tone4:"] = "\U0001f486\U0001f3fe\u200d\u2640\ufe0f",
[":woman_getting_face_massage_medium_dark_skin_tone:"] = "\U0001f486\U0001f3fe\u200d\u2640\ufe0f",
[":woman_getting_face_massage::skin-tone-4:"] = "\U0001f486\U0001f3fe\u200d\u2640\ufe0f",
[":woman_getting_face_massage_tone5:"] = "\U0001f486\U0001f3ff\u200d\u2640\ufe0f",
[":woman_getting_face_massage_dark_skin_tone:"] = "\U0001f486\U0001f3ff\u200d\u2640\ufe0f",
[":woman_getting_face_massage::skin-tone-5:"] = "\U0001f486\U0001f3ff\u200d\u2640\ufe0f",
[":woman_getting_haircut:"] = "\U0001f487\u200d\u2640\ufe0f",
[":woman_getting_haircut_tone1:"] = "\U0001f487\U0001f3fb\u200d\u2640\ufe0f",
[":woman_getting_haircut_light_skin_tone:"] = "\U0001f487\U0001f3fb\u200d\u2640\ufe0f",
[":woman_getting_haircut::skin-tone-1:"] = "\U0001f487\U0001f3fb\u200d\u2640\ufe0f",
[":woman_getting_haircut_tone2:"] = "\U0001f487\U0001f3fc\u200d\u2640\ufe0f",
[":woman_getting_haircut_medium_light_skin_tone:"] = "\U0001f487\U0001f3fc\u200d\u2640\ufe0f",
[":woman_getting_haircut::skin-tone-2:"] = "\U0001f487\U0001f3fc\u200d\u2640\ufe0f",
[":woman_getting_haircut_tone3:"] = "\U0001f487\U0001f3fd\u200d\u2640\ufe0f",
[":woman_getting_haircut_medium_skin_tone:"] = "\U0001f487\U0001f3fd\u200d\u2640\ufe0f",
[":woman_getting_haircut::skin-tone-3:"] = "\U0001f487\U0001f3fd\u200d\u2640\ufe0f",
[":woman_getting_haircut_tone4:"] = "\U0001f487\U0001f3fe\u200d\u2640\ufe0f",
[":woman_getting_haircut_medium_dark_skin_tone:"] = "\U0001f487\U0001f3fe\u200d\u2640\ufe0f",
[":woman_getting_haircut::skin-tone-4:"] = "\U0001f487\U0001f3fe\u200d\u2640\ufe0f",
[":woman_getting_haircut_tone5:"] = "\U0001f487\U0001f3ff\u200d\u2640\ufe0f",
[":woman_getting_haircut_dark_skin_tone:"] = "\U0001f487\U0001f3ff\u200d\u2640\ufe0f",
[":woman_getting_haircut::skin-tone-5:"] = "\U0001f487\U0001f3ff\u200d\u2640\ufe0f",
[":woman_golfing:"] = "\U0001f3cc\ufe0f\u200d\u2640\ufe0f",
[":woman_golfing_tone1:"] = "\U0001f3cc\U0001f3fb\u200d\u2640\ufe0f",
[":woman_golfing_light_skin_tone:"] = "\U0001f3cc\U0001f3fb\u200d\u2640\ufe0f",
[":woman_golfing::skin-tone-1:"] = "\U0001f3cc\U0001f3fb\u200d\u2640\ufe0f",
[":woman_golfing_tone2:"] = "\U0001f3cc\U0001f3fc\u200d\u2640\ufe0f",
[":woman_golfing_medium_light_skin_tone:"] = "\U0001f3cc\U0001f3fc\u200d\u2640\ufe0f",
[":woman_golfing::skin-tone-2:"] = "\U0001f3cc\U0001f3fc\u200d\u2640\ufe0f",
[":woman_golfing_tone3:"] = "\U0001f3cc\U0001f3fd\u200d\u2640\ufe0f",
[":woman_golfing_medium_skin_tone:"] = "\U0001f3cc\U0001f3fd\u200d\u2640\ufe0f",
[":woman_golfing::skin-tone-3:"] = "\U0001f3cc\U0001f3fd\u200d\u2640\ufe0f",
[":woman_golfing_tone4:"] = "\U0001f3cc\U0001f3fe\u200d\u2640\ufe0f",
[":woman_golfing_medium_dark_skin_tone:"] = "\U0001f3cc\U0001f3fe\u200d\u2640\ufe0f",
[":woman_golfing::skin-tone-4:"] = "\U0001f3cc\U0001f3fe\u200d\u2640\ufe0f",
[":woman_golfing_tone5:"] = "\U0001f3cc\U0001f3ff\u200d\u2640\ufe0f",
[":woman_golfing_dark_skin_tone:"] = "\U0001f3cc\U0001f3ff\u200d\u2640\ufe0f",
[":woman_golfing::skin-tone-5:"] = "\U0001f3cc\U0001f3ff\u200d\u2640\ufe0f",
[":woman_guard:"] = "\U0001f482\u200d\u2640\ufe0f",
[":woman_guard_tone1:"] = "\U0001f482\U0001f3fb\u200d\u2640\ufe0f",
[":woman_guard_light_skin_tone:"] = "\U0001f482\U0001f3fb\u200d\u2640\ufe0f",
[":woman_guard::skin-tone-1:"] = "\U0001f482\U0001f3fb\u200d\u2640\ufe0f",
[":woman_guard_tone2:"] = "\U0001f482\U0001f3fc\u200d\u2640\ufe0f",
[":woman_guard_medium_light_skin_tone:"] = "\U0001f482\U0001f3fc\u200d\u2640\ufe0f",
[":woman_guard::skin-tone-2:"] = "\U0001f482\U0001f3fc\u200d\u2640\ufe0f",
[":woman_guard_tone3:"] = "\U0001f482\U0001f3fd\u200d\u2640\ufe0f",
[":woman_guard_medium_skin_tone:"] = "\U0001f482\U0001f3fd\u200d\u2640\ufe0f",
[":woman_guard::skin-tone-3:"] = "\U0001f482\U0001f3fd\u200d\u2640\ufe0f",
[":woman_guard_tone4:"] = "\U0001f482\U0001f3fe\u200d\u2640\ufe0f",
[":woman_guard_medium_dark_skin_tone:"] = "\U0001f482\U0001f3fe\u200d\u2640\ufe0f",
[":woman_guard::skin-tone-4:"] = "\U0001f482\U0001f3fe\u200d\u2640\ufe0f",
[":woman_guard_tone5:"] = "\U0001f482\U0001f3ff\u200d\u2640\ufe0f",
[":woman_guard_dark_skin_tone:"] = "\U0001f482\U0001f3ff\u200d\u2640\ufe0f",
[":woman_guard::skin-tone-5:"] = "\U0001f482\U0001f3ff\u200d\u2640\ufe0f",
[":woman_health_worker:"] = "\U0001f469\u200d\u2695\ufe0f",
[":woman_health_worker_tone1:"] = "\U0001f469\U0001f3fb\u200d\u2695\ufe0f",
[":woman_health_worker_light_skin_tone:"] = "\U0001f469\U0001f3fb\u200d\u2695\ufe0f",
[":woman_health_worker::skin-tone-1:"] = "\U0001f469\U0001f3fb\u200d\u2695\ufe0f",
[":woman_health_worker_tone2:"] = "\U0001f469\U0001f3fc\u200d\u2695\ufe0f",
[":woman_health_worker_medium_light_skin_tone:"] = "\U0001f469\U0001f3fc\u200d\u2695\ufe0f",
[":woman_health_worker::skin-tone-2:"] = "\U0001f469\U0001f3fc\u200d\u2695\ufe0f",
[":woman_health_worker_tone3:"] = "\U0001f469\U0001f3fd\u200d\u2695\ufe0f",
[":woman_health_worker_medium_skin_tone:"] = "\U0001f469\U0001f3fd\u200d\u2695\ufe0f",
[":woman_health_worker::skin-tone-3:"] = "\U0001f469\U0001f3fd\u200d\u2695\ufe0f",
[":woman_health_worker_tone4:"] = "\U0001f469\U0001f3fe\u200d\u2695\ufe0f",
[":woman_health_worker_medium_dark_skin_tone:"] = "\U0001f469\U0001f3fe\u200d\u2695\ufe0f",
[":woman_health_worker::skin-tone-4:"] = "\U0001f469\U0001f3fe\u200d\u2695\ufe0f",
[":woman_health_worker_tone5:"] = "\U0001f469\U0001f3ff\u200d\u2695\ufe0f",
[":woman_health_worker_dark_skin_tone:"] = "\U0001f469\U0001f3ff\u200d\u2695\ufe0f",
[":woman_health_worker::skin-tone-5:"] = "\U0001f469\U0001f3ff\u200d\u2695\ufe0f",
[":woman_in_lotus_position:"] = "\U0001f9d8\u200d\u2640\ufe0f",
[":woman_in_lotus_position_tone1:"] = "\U0001f9d8\U0001f3fb\u200d\u2640\ufe0f",
[":woman_in_lotus_position_light_skin_tone:"] = "\U0001f9d8\U0001f3fb\u200d\u2640\ufe0f",
[":woman_in_lotus_position::skin-tone-1:"] = "\U0001f9d8\U0001f3fb\u200d\u2640\ufe0f",
[":woman_in_lotus_position_tone2:"] = "\U0001f9d8\U0001f3fc\u200d\u2640\ufe0f",
[":woman_in_lotus_position_medium_light_skin_tone:"] = "\U0001f9d8\U0001f3fc\u200d\u2640\ufe0f",
[":woman_in_lotus_position::skin-tone-2:"] = "\U0001f9d8\U0001f3fc\u200d\u2640\ufe0f",
[":woman_in_lotus_position_tone3:"] = "\U0001f9d8\U0001f3fd\u200d\u2640\ufe0f",
[":woman_in_lotus_position_medium_skin_tone:"] = "\U0001f9d8\U0001f3fd\u200d\u2640\ufe0f",
[":woman_in_lotus_position::skin-tone-3:"] = "\U0001f9d8\U0001f3fd\u200d\u2640\ufe0f",
[":woman_in_lotus_position_tone4:"] = "\U0001f9d8\U0001f3fe\u200d\u2640\ufe0f",
[":woman_in_lotus_position_medium_dark_skin_tone:"] = "\U0001f9d8\U0001f3fe\u200d\u2640\ufe0f",
[":woman_in_lotus_position::skin-tone-4:"] = "\U0001f9d8\U0001f3fe\u200d\u2640\ufe0f",
[":woman_in_lotus_position_tone5:"] = "\U0001f9d8\U0001f3ff\u200d\u2640\ufe0f",
[":woman_in_lotus_position_dark_skin_tone:"] = "\U0001f9d8\U0001f3ff\u200d\u2640\ufe0f",
[":woman_in_lotus_position::skin-tone-5:"] = "\U0001f9d8\U0001f3ff\u200d\u2640\ufe0f",
[":woman_in_manual_wheelchair:"] = "\U0001f469\u200d\U0001f9bd",
[":woman_in_manual_wheelchair_tone1:"] = "\U0001f469\U0001f3fb\u200d\U0001f9bd",
[":woman_in_manual_wheelchair_light_skin_tone:"] = "\U0001f469\U0001f3fb\u200d\U0001f9bd",
[":woman_in_manual_wheelchair::skin-tone-1:"] = "\U0001f469\U0001f3fb\u200d\U0001f9bd",
[":woman_in_manual_wheelchair_tone2:"] = "\U0001f469\U0001f3fc\u200d\U0001f9bd",
[":woman_in_manual_wheelchair_medium_light_skin_tone:"] = "\U0001f469\U0001f3fc\u200d\U0001f9bd",
[":woman_in_manual_wheelchair::skin-tone-2:"] = "\U0001f469\U0001f3fc\u200d\U0001f9bd",
[":woman_in_manual_wheelchair_tone3:"] = "\U0001f469\U0001f3fd\u200d\U0001f9bd",
[":woman_in_manual_wheelchair_medium_skin_tone:"] = "\U0001f469\U0001f3fd\u200d\U0001f9bd",
[":woman_in_manual_wheelchair::skin-tone-3:"] = "\U0001f469\U0001f3fd\u200d\U0001f9bd",
[":woman_in_manual_wheelchair_tone4:"] = "\U0001f469\U0001f3fe\u200d\U0001f9bd",
[":woman_in_manual_wheelchair_medium_dark_skin_tone:"] = "\U0001f469\U0001f3fe\u200d\U0001f9bd",
[":woman_in_manual_wheelchair::skin-tone-4:"] = "\U0001f469\U0001f3fe\u200d\U0001f9bd",
[":woman_in_manual_wheelchair_tone5:"] = "\U0001f469\U0001f3ff\u200d\U0001f9bd",
[":woman_in_manual_wheelchair_dark_skin_tone:"] = "\U0001f469\U0001f3ff\u200d\U0001f9bd",
[":woman_in_manual_wheelchair::skin-tone-5:"] = "\U0001f469\U0001f3ff\u200d\U0001f9bd",
[":woman_in_motorized_wheelchair:"] = "\U0001f469\u200d\U0001f9bc",
[":woman_in_motorized_wheelchair_tone1:"] = "\U0001f469\U0001f3fb\u200d\U0001f9bc",
[":woman_in_motorized_wheelchair_light_skin_tone:"] = "\U0001f469\U0001f3fb\u200d\U0001f9bc",
[":woman_in_motorized_wheelchair::skin-tone-1:"] = "\U0001f469\U0001f3fb\u200d\U0001f9bc",
[":woman_in_motorized_wheelchair_tone2:"] = "\U0001f469\U0001f3fc\u200d\U0001f9bc",
[":woman_in_motorized_wheelchair_medium_light_skin_tone:"] = "\U0001f469\U0001f3fc\u200d\U0001f9bc",
[":woman_in_motorized_wheelchair::skin-tone-2:"] = "\U0001f469\U0001f3fc\u200d\U0001f9bc",
[":woman_in_motorized_wheelchair_tone3:"] = "\U0001f469\U0001f3fd\u200d\U0001f9bc",
[":woman_in_motorized_wheelchair_medium_skin_tone:"] = "\U0001f469\U0001f3fd\u200d\U0001f9bc",
[":woman_in_motorized_wheelchair::skin-tone-3:"] = "\U0001f469\U0001f3fd\u200d\U0001f9bc",
[":woman_in_motorized_wheelchair_tone4:"] = "\U0001f469\U0001f3fe\u200d\U0001f9bc",
[":woman_in_motorized_wheelchair_medium_dark_skin_tone:"] = "\U0001f469\U0001f3fe\u200d\U0001f9bc",
[":woman_in_motorized_wheelchair::skin-tone-4:"] = "\U0001f469\U0001f3fe\u200d\U0001f9bc",
[":woman_in_motorized_wheelchair_tone5:"] = "\U0001f469\U0001f3ff\u200d\U0001f9bc",
[":woman_in_motorized_wheelchair_dark_skin_tone:"] = "\U0001f469\U0001f3ff\u200d\U0001f9bc",
[":woman_in_motorized_wheelchair::skin-tone-5:"] = "\U0001f469\U0001f3ff\u200d\U0001f9bc",
[":woman_in_steamy_room:"] = "\U0001f9d6\u200d\u2640\ufe0f",
[":woman_in_steamy_room_tone1:"] = "\U0001f9d6\U0001f3fb\u200d\u2640\ufe0f",
[":woman_in_steamy_room_light_skin_tone:"] = "\U0001f9d6\U0001f3fb\u200d\u2640\ufe0f",
[":woman_in_steamy_room::skin-tone-1:"] = "\U0001f9d6\U0001f3fb\u200d\u2640\ufe0f",
[":woman_in_steamy_room_tone2:"] = "\U0001f9d6\U0001f3fc\u200d\u2640\ufe0f",
[":woman_in_steamy_room_medium_light_skin_tone:"] = "\U0001f9d6\U0001f3fc\u200d\u2640\ufe0f",
[":woman_in_steamy_room::skin-tone-2:"] = "\U0001f9d6\U0001f3fc\u200d\u2640\ufe0f",
[":woman_in_steamy_room_tone3:"] = "\U0001f9d6\U0001f3fd\u200d\u2640\ufe0f",
[":woman_in_steamy_room_medium_skin_tone:"] = "\U0001f9d6\U0001f3fd\u200d\u2640\ufe0f",
[":woman_in_steamy_room::skin-tone-3:"] = "\U0001f9d6\U0001f3fd\u200d\u2640\ufe0f",
[":woman_in_steamy_room_tone4:"] = "\U0001f9d6\U0001f3fe\u200d\u2640\ufe0f",
[":woman_in_steamy_room_medium_dark_skin_tone:"] = "\U0001f9d6\U0001f3fe\u200d\u2640\ufe0f",
[":woman_in_steamy_room::skin-tone-4:"] = "\U0001f9d6\U0001f3fe\u200d\u2640\ufe0f",
[":woman_in_steamy_room_tone5:"] = "\U0001f9d6\U0001f3ff\u200d\u2640\ufe0f",
[":woman_in_steamy_room_dark_skin_tone:"] = "\U0001f9d6\U0001f3ff\u200d\u2640\ufe0f",
[":woman_in_steamy_room::skin-tone-5:"] = "\U0001f9d6\U0001f3ff\u200d\u2640\ufe0f",
[":woman_in_tuxedo:"] = "\U0001f935\u200d\u2640\ufe0f",
[":woman_in_tuxedo_tone1:"] = "\U0001f935\U0001f3fb\u200d\u2640\ufe0f",
[":woman_in_tuxedo_light_skin_tone:"] = "\U0001f935\U0001f3fb\u200d\u2640\ufe0f",
[":woman_in_tuxedo::skin-tone-1:"] = "\U0001f935\U0001f3fb\u200d\u2640\ufe0f",
[":woman_in_tuxedo_tone2:"] = "\U0001f935\U0001f3fc\u200d\u2640\ufe0f",
[":woman_in_tuxedo_medium_light_skin_tone:"] = "\U0001f935\U0001f3fc\u200d\u2640\ufe0f",
[":woman_in_tuxedo::skin-tone-2:"] = "\U0001f935\U0001f3fc\u200d\u2640\ufe0f",
[":woman_in_tuxedo_tone3:"] = "\U0001f935\U0001f3fd\u200d\u2640\ufe0f",
[":woman_in_tuxedo_medium_skin_tone:"] = "\U0001f935\U0001f3fd\u200d\u2640\ufe0f",
[":woman_in_tuxedo::skin-tone-3:"] = "\U0001f935\U0001f3fd\u200d\u2640\ufe0f",
[":woman_in_tuxedo_tone4:"] = "\U0001f935\U0001f3fe\u200d\u2640\ufe0f",
[":woman_in_tuxedo_medium_dark_skin_tone:"] = "\U0001f935\U0001f3fe\u200d\u2640\ufe0f",
[":woman_in_tuxedo::skin-tone-4:"] = "\U0001f935\U0001f3fe\u200d\u2640\ufe0f",
[":woman_in_tuxedo_tone5:"] = "\U0001f935\U0001f3ff\u200d\u2640\ufe0f",
[":woman_in_tuxedo_dark_skin_tone:"] = "\U0001f935\U0001f3ff\u200d\u2640\ufe0f",
[":woman_in_tuxedo::skin-tone-5:"] = "\U0001f935\U0001f3ff\u200d\u2640\ufe0f",
[":woman_judge:"] = "\U0001f469\u200d\u2696\ufe0f",
[":woman_judge_tone1:"] = "\U0001f469\U0001f3fb\u200d\u2696\ufe0f",
[":woman_judge_light_skin_tone:"] = "\U0001f469\U0001f3fb\u200d\u2696\ufe0f",
[":woman_judge::skin-tone-1:"] = "\U0001f469\U0001f3fb\u200d\u2696\ufe0f",
[":woman_judge_tone2:"] = "\U0001f469\U0001f3fc\u200d\u2696\ufe0f",
[":woman_judge_medium_light_skin_tone:"] = "\U0001f469\U0001f3fc\u200d\u2696\ufe0f",
[":woman_judge::skin-tone-2:"] = "\U0001f469\U0001f3fc\u200d\u2696\ufe0f",
[":woman_judge_tone3:"] = "\U0001f469\U0001f3fd\u200d\u2696\ufe0f",
[":woman_judge_medium_skin_tone:"] = "\U0001f469\U0001f3fd\u200d\u2696\ufe0f",
[":woman_judge::skin-tone-3:"] = "\U0001f469\U0001f3fd\u200d\u2696\ufe0f",
[":woman_judge_tone4:"] = "\U0001f469\U0001f3fe\u200d\u2696\ufe0f",
[":woman_judge_medium_dark_skin_tone:"] = "\U0001f469\U0001f3fe\u200d\u2696\ufe0f",
[":woman_judge::skin-tone-4:"] = "\U0001f469\U0001f3fe\u200d\u2696\ufe0f",
[":woman_judge_tone5:"] = "\U0001f469\U0001f3ff\u200d\u2696\ufe0f",
[":woman_judge_dark_skin_tone:"] = "\U0001f469\U0001f3ff\u200d\u2696\ufe0f",
[":woman_judge::skin-tone-5:"] = "\U0001f469\U0001f3ff\u200d\u2696\ufe0f",
[":woman_juggling:"] = "\U0001f939\u200d\u2640\ufe0f",
[":woman_juggling_tone1:"] = "\U0001f939\U0001f3fb\u200d\u2640\ufe0f",
[":woman_juggling_light_skin_tone:"] = "\U0001f939\U0001f3fb\u200d\u2640\ufe0f",
[":woman_juggling::skin-tone-1:"] = "\U0001f939\U0001f3fb\u200d\u2640\ufe0f",
[":woman_juggling_tone2:"] = "\U0001f939\U0001f3fc\u200d\u2640\ufe0f",
[":woman_juggling_medium_light_skin_tone:"] = "\U0001f939\U0001f3fc\u200d\u2640\ufe0f",
[":woman_juggling::skin-tone-2:"] = "\U0001f939\U0001f3fc\u200d\u2640\ufe0f",
[":woman_juggling_tone3:"] = "\U0001f939\U0001f3fd\u200d\u2640\ufe0f",
[":woman_juggling_medium_skin_tone:"] = "\U0001f939\U0001f3fd\u200d\u2640\ufe0f",
[":woman_juggling::skin-tone-3:"] = "\U0001f939\U0001f3fd\u200d\u2640\ufe0f",
[":woman_juggling_tone4:"] = "\U0001f939\U0001f3fe\u200d\u2640\ufe0f",
[":woman_juggling_medium_dark_skin_tone:"] = "\U0001f939\U0001f3fe\u200d\u2640\ufe0f",
[":woman_juggling::skin-tone-4:"] = "\U0001f939\U0001f3fe\u200d\u2640\ufe0f",
[":woman_juggling_tone5:"] = "\U0001f939\U0001f3ff\u200d\u2640\ufe0f",
[":woman_juggling_dark_skin_tone:"] = "\U0001f939\U0001f3ff\u200d\u2640\ufe0f",
[":woman_juggling::skin-tone-5:"] = "\U0001f939\U0001f3ff\u200d\u2640\ufe0f",
[":woman_kneeling:"] = "\U0001f9ce\u200d\u2640\ufe0f",
[":woman_kneeling_tone1:"] = "\U0001f9ce\U0001f3fb\u200d\u2640\ufe0f",
[":woman_kneeling_light_skin_tone:"] = "\U0001f9ce\U0001f3fb\u200d\u2640\ufe0f",
[":woman_kneeling::skin-tone-1:"] = "\U0001f9ce\U0001f3fb\u200d\u2640\ufe0f",
[":woman_kneeling_tone2:"] = "\U0001f9ce\U0001f3fc\u200d\u2640\ufe0f",
[":woman_kneeling_medium_light_skin_tone:"] = "\U0001f9ce\U0001f3fc\u200d\u2640\ufe0f",
[":woman_kneeling::skin-tone-2:"] = "\U0001f9ce\U0001f3fc\u200d\u2640\ufe0f",
[":woman_kneeling_tone3:"] = "\U0001f9ce\U0001f3fd\u200d\u2640\ufe0f",
[":woman_kneeling_medium_skin_tone:"] = "\U0001f9ce\U0001f3fd\u200d\u2640\ufe0f",
[":woman_kneeling::skin-tone-3:"] = "\U0001f9ce\U0001f3fd\u200d\u2640\ufe0f",
[":woman_kneeling_tone4:"] = "\U0001f9ce\U0001f3fe\u200d\u2640\ufe0f",
[":woman_kneeling_medium_dark_skin_tone:"] = "\U0001f9ce\U0001f3fe\u200d\u2640\ufe0f",
[":woman_kneeling::skin-tone-4:"] = "\U0001f9ce\U0001f3fe\u200d\u2640\ufe0f",
[":woman_kneeling_tone5:"] = "\U0001f9ce\U0001f3ff\u200d\u2640\ufe0f",
[":woman_kneeling_dark_skin_tone:"] = "\U0001f9ce\U0001f3ff\u200d\u2640\ufe0f",
[":woman_kneeling::skin-tone-5:"] = "\U0001f9ce\U0001f3ff\u200d\u2640\ufe0f",
[":woman_lifting_weights:"] = "\U0001f3cb\ufe0f\u200d\u2640\ufe0f",
[":woman_lifting_weights_tone1:"] = "\U0001f3cb\U0001f3fb\u200d\u2640\ufe0f",
[":woman_lifting_weights_light_skin_tone:"] = "\U0001f3cb\U0001f3fb\u200d\u2640\ufe0f",
[":woman_lifting_weights::skin-tone-1:"] = "\U0001f3cb\U0001f3fb\u200d\u2640\ufe0f",
[":woman_lifting_weights_tone2:"] = "\U0001f3cb\U0001f3fc\u200d\u2640\ufe0f",
[":woman_lifting_weights_medium_light_skin_tone:"] = "\U0001f3cb\U0001f3fc\u200d\u2640\ufe0f",
[":woman_lifting_weights::skin-tone-2:"] = "\U0001f3cb\U0001f3fc\u200d\u2640\ufe0f",
[":woman_lifting_weights_tone3:"] = "\U0001f3cb\U0001f3fd\u200d\u2640\ufe0f",
[":woman_lifting_weights_medium_skin_tone:"] = "\U0001f3cb\U0001f3fd\u200d\u2640\ufe0f",
[":woman_lifting_weights::skin-tone-3:"] = "\U0001f3cb\U0001f3fd\u200d\u2640\ufe0f",
[":woman_lifting_weights_tone4:"] = "\U0001f3cb\U0001f3fe\u200d\u2640\ufe0f",
[":woman_lifting_weights_medium_dark_skin_tone:"] = "\U0001f3cb\U0001f3fe\u200d\u2640\ufe0f",
[":woman_lifting_weights::skin-tone-4:"] = "\U0001f3cb\U0001f3fe\u200d\u2640\ufe0f",
[":woman_lifting_weights_tone5:"] = "\U0001f3cb\U0001f3ff\u200d\u2640\ufe0f",
[":woman_lifting_weights_dark_skin_tone:"] = "\U0001f3cb\U0001f3ff\u200d\u2640\ufe0f",
[":woman_lifting_weights::skin-tone-5:"] = "\U0001f3cb\U0001f3ff\u200d\u2640\ufe0f",
[":woman_mage:"] = "\U0001f9d9\u200d\u2640\ufe0f",
[":woman_mage_tone1:"] = "\U0001f9d9\U0001f3fb\u200d\u2640\ufe0f",
[":woman_mage_light_skin_tone:"] = "\U0001f9d9\U0001f3fb\u200d\u2640\ufe0f",
[":woman_mage::skin-tone-1:"] = "\U0001f9d9\U0001f3fb\u200d\u2640\ufe0f",
[":woman_mage_tone2:"] = "\U0001f9d9\U0001f3fc\u200d\u2640\ufe0f",
[":woman_mage_medium_light_skin_tone:"] = "\U0001f9d9\U0001f3fc\u200d\u2640\ufe0f",
[":woman_mage::skin-tone-2:"] = "\U0001f9d9\U0001f3fc\u200d\u2640\ufe0f",
[":woman_mage_tone3:"] = "\U0001f9d9\U0001f3fd\u200d\u2640\ufe0f",
[":woman_mage_medium_skin_tone:"] = "\U0001f9d9\U0001f3fd\u200d\u2640\ufe0f",
[":woman_mage::skin-tone-3:"] = "\U0001f9d9\U0001f3fd\u200d\u2640\ufe0f",
[":woman_mage_tone4:"] = "\U0001f9d9\U0001f3fe\u200d\u2640\ufe0f",
[":woman_mage_medium_dark_skin_tone:"] = "\U0001f9d9\U0001f3fe\u200d\u2640\ufe0f",
[":woman_mage::skin-tone-4:"] = "\U0001f9d9\U0001f3fe\u200d\u2640\ufe0f",
[":woman_mage_tone5:"] = "\U0001f9d9\U0001f3ff\u200d\u2640\ufe0f",
[":woman_mage_dark_skin_tone:"] = "\U0001f9d9\U0001f3ff\u200d\u2640\ufe0f",
[":woman_mage::skin-tone-5:"] = "\U0001f9d9\U0001f3ff\u200d\u2640\ufe0f",
[":woman_mechanic:"] = "\U0001f469\u200d\U0001f527",
[":woman_mechanic_tone1:"] = "\U0001f469\U0001f3fb\u200d\U0001f527",
[":woman_mechanic_light_skin_tone:"] = "\U0001f469\U0001f3fb\u200d\U0001f527",
[":woman_mechanic::skin-tone-1:"] = "\U0001f469\U0001f3fb\u200d\U0001f527",
[":woman_mechanic_tone2:"] = "\U0001f469\U0001f3fc\u200d\U0001f527",
[":woman_mechanic_medium_light_skin_tone:"] = "\U0001f469\U0001f3fc\u200d\U0001f527",
[":woman_mechanic::skin-tone-2:"] = "\U0001f469\U0001f3fc\u200d\U0001f527",
[":woman_mechanic_tone3:"] = "\U0001f469\U0001f3fd\u200d\U0001f527",
[":woman_mechanic_medium_skin_tone:"] = "\U0001f469\U0001f3fd\u200d\U0001f527",
[":woman_mechanic::skin-tone-3:"] = "\U0001f469\U0001f3fd\u200d\U0001f527",
[":woman_mechanic_tone4:"] = "\U0001f469\U0001f3fe\u200d\U0001f527",
[":woman_mechanic_medium_dark_skin_tone:"] = "\U0001f469\U0001f3fe\u200d\U0001f527",
[":woman_mechanic::skin-tone-4:"] = "\U0001f469\U0001f3fe\u200d\U0001f527",
[":woman_mechanic_tone5:"] = "\U0001f469\U0001f3ff\u200d\U0001f527",
[":woman_mechanic_dark_skin_tone:"] = "\U0001f469\U0001f3ff\u200d\U0001f527",
[":woman_mechanic::skin-tone-5:"] = "\U0001f469\U0001f3ff\u200d\U0001f527",
[":woman_mountain_biking:"] = "\U0001f6b5\u200d\u2640\ufe0f",
[":woman_mountain_biking_tone1:"] = "\U0001f6b5\U0001f3fb\u200d\u2640\ufe0f",
[":woman_mountain_biking_light_skin_tone:"] = "\U0001f6b5\U0001f3fb\u200d\u2640\ufe0f",
[":woman_mountain_biking::skin-tone-1:"] = "\U0001f6b5\U0001f3fb\u200d\u2640\ufe0f",
[":woman_mountain_biking_tone2:"] = "\U0001f6b5\U0001f3fc\u200d\u2640\ufe0f",
[":woman_mountain_biking_medium_light_skin_tone:"] = "\U0001f6b5\U0001f3fc\u200d\u2640\ufe0f",
[":woman_mountain_biking::skin-tone-2:"] = "\U0001f6b5\U0001f3fc\u200d\u2640\ufe0f",
[":woman_mountain_biking_tone3:"] = "\U0001f6b5\U0001f3fd\u200d\u2640\ufe0f",
[":woman_mountain_biking_medium_skin_tone:"] = "\U0001f6b5\U0001f3fd\u200d\u2640\ufe0f",
[":woman_mountain_biking::skin-tone-3:"] = "\U0001f6b5\U0001f3fd\u200d\u2640\ufe0f",
[":woman_mountain_biking_tone4:"] = "\U0001f6b5\U0001f3fe\u200d\u2640\ufe0f",
[":woman_mountain_biking_medium_dark_skin_tone:"] = "\U0001f6b5\U0001f3fe\u200d\u2640\ufe0f",
[":woman_mountain_biking::skin-tone-4:"] = "\U0001f6b5\U0001f3fe\u200d\u2640\ufe0f",
[":woman_mountain_biking_tone5:"] = "\U0001f6b5\U0001f3ff\u200d\u2640\ufe0f",
[":woman_mountain_biking_dark_skin_tone:"] = "\U0001f6b5\U0001f3ff\u200d\u2640\ufe0f",
[":woman_mountain_biking::skin-tone-5:"] = "\U0001f6b5\U0001f3ff\u200d\u2640\ufe0f",
[":woman_office_worker:"] = "\U0001f469\u200d\U0001f4bc",
[":woman_office_worker_tone1:"] = "\U0001f469\U0001f3fb\u200d\U0001f4bc",
[":woman_office_worker_light_skin_tone:"] = "\U0001f469\U0001f3fb\u200d\U0001f4bc",
[":woman_office_worker::skin-tone-1:"] = "\U0001f469\U0001f3fb\u200d\U0001f4bc",
[":woman_office_worker_tone2:"] = "\U0001f469\U0001f3fc\u200d\U0001f4bc",
[":woman_office_worker_medium_light_skin_tone:"] = "\U0001f469\U0001f3fc\u200d\U0001f4bc",
[":woman_office_worker::skin-tone-2:"] = "\U0001f469\U0001f3fc\u200d\U0001f4bc",
[":woman_office_worker_tone3:"] = "\U0001f469\U0001f3fd\u200d\U0001f4bc",
[":woman_office_worker_medium_skin_tone:"] = "\U0001f469\U0001f3fd\u200d\U0001f4bc",
[":woman_office_worker::skin-tone-3:"] = "\U0001f469\U0001f3fd\u200d\U0001f4bc",
[":woman_office_worker_tone4:"] = "\U0001f469\U0001f3fe\u200d\U0001f4bc",
[":woman_office_worker_medium_dark_skin_tone:"] = "\U0001f469\U0001f3fe\u200d\U0001f4bc",
[":woman_office_worker::skin-tone-4:"] = "\U0001f469\U0001f3fe\u200d\U0001f4bc",
[":woman_office_worker_tone5:"] = "\U0001f469\U0001f3ff\u200d\U0001f4bc",
[":woman_office_worker_dark_skin_tone:"] = "\U0001f469\U0001f3ff\u200d\U0001f4bc",
[":woman_office_worker::skin-tone-5:"] = "\U0001f469\U0001f3ff\u200d\U0001f4bc",
[":woman_pilot:"] = "\U0001f469\u200d\u2708\ufe0f",
[":woman_pilot_tone1:"] = "\U0001f469\U0001f3fb\u200d\u2708\ufe0f",
[":woman_pilot_light_skin_tone:"] = "\U0001f469\U0001f3fb\u200d\u2708\ufe0f",
[":woman_pilot::skin-tone-1:"] = "\U0001f469\U0001f3fb\u200d\u2708\ufe0f",
[":woman_pilot_tone2:"] = "\U0001f469\U0001f3fc\u200d\u2708\ufe0f",
[":woman_pilot_medium_light_skin_tone:"] = "\U0001f469\U0001f3fc\u200d\u2708\ufe0f",
[":woman_pilot::skin-tone-2:"] = "\U0001f469\U0001f3fc\u200d\u2708\ufe0f",
[":woman_pilot_tone3:"] = "\U0001f469\U0001f3fd\u200d\u2708\ufe0f",
[":woman_pilot_medium_skin_tone:"] = "\U0001f469\U0001f3fd\u200d\u2708\ufe0f",
[":woman_pilot::skin-tone-3:"] = "\U0001f469\U0001f3fd\u200d\u2708\ufe0f",
[":woman_pilot_tone4:"] = "\U0001f469\U0001f3fe\u200d\u2708\ufe0f",
[":woman_pilot_medium_dark_skin_tone:"] = "\U0001f469\U0001f3fe\u200d\u2708\ufe0f",
[":woman_pilot::skin-tone-4:"] = "\U0001f469\U0001f3fe\u200d\u2708\ufe0f",
[":woman_pilot_tone5:"] = "\U0001f469\U0001f3ff\u200d\u2708\ufe0f",
[":woman_pilot_dark_skin_tone:"] = "\U0001f469\U0001f3ff\u200d\u2708\ufe0f",
[":woman_pilot::skin-tone-5:"] = "\U0001f469\U0001f3ff\u200d\u2708\ufe0f",
[":woman_playing_handball:"] = "\U0001f93e\u200d\u2640\ufe0f",
[":woman_playing_handball_tone1:"] = "\U0001f93e\U0001f3fb\u200d\u2640\ufe0f",
[":woman_playing_handball_light_skin_tone:"] = "\U0001f93e\U0001f3fb\u200d\u2640\ufe0f",
[":woman_playing_handball::skin-tone-1:"] = "\U0001f93e\U0001f3fb\u200d\u2640\ufe0f",
[":woman_playing_handball_tone2:"] = "\U0001f93e\U0001f3fc\u200d\u2640\ufe0f",
[":woman_playing_handball_medium_light_skin_tone:"] = "\U0001f93e\U0001f3fc\u200d\u2640\ufe0f",
[":woman_playing_handball::skin-tone-2:"] = "\U0001f93e\U0001f3fc\u200d\u2640\ufe0f",
[":woman_playing_handball_tone3:"] = "\U0001f93e\U0001f3fd\u200d\u2640\ufe0f",
[":woman_playing_handball_medium_skin_tone:"] = "\U0001f93e\U0001f3fd\u200d\u2640\ufe0f",
[":woman_playing_handball::skin-tone-3:"] = "\U0001f93e\U0001f3fd\u200d\u2640\ufe0f",
[":woman_playing_handball_tone4:"] = "\U0001f93e\U0001f3fe\u200d\u2640\ufe0f",
[":woman_playing_handball_medium_dark_skin_tone:"] = "\U0001f93e\U0001f3fe\u200d\u2640\ufe0f",
[":woman_playing_handball::skin-tone-4:"] = "\U0001f93e\U0001f3fe\u200d\u2640\ufe0f",
[":woman_playing_handball_tone5:"] = "\U0001f93e\U0001f3ff\u200d\u2640\ufe0f",
[":woman_playing_handball_dark_skin_tone:"] = "\U0001f93e\U0001f3ff\u200d\u2640\ufe0f",
[":woman_playing_handball::skin-tone-5:"] = "\U0001f93e\U0001f3ff\u200d\u2640\ufe0f",
[":woman_playing_water_polo:"] = "\U0001f93d\u200d\u2640\ufe0f",
[":woman_playing_water_polo_tone1:"] = "\U0001f93d\U0001f3fb\u200d\u2640\ufe0f",
[":woman_playing_water_polo_light_skin_tone:"] = "\U0001f93d\U0001f3fb\u200d\u2640\ufe0f",
[":woman_playing_water_polo::skin-tone-1:"] = "\U0001f93d\U0001f3fb\u200d\u2640\ufe0f",
[":woman_playing_water_polo_tone2:"] = "\U0001f93d\U0001f3fc\u200d\u2640\ufe0f",
[":woman_playing_water_polo_medium_light_skin_tone:"] = "\U0001f93d\U0001f3fc\u200d\u2640\ufe0f",
[":woman_playing_water_polo::skin-tone-2:"] = "\U0001f93d\U0001f3fc\u200d\u2640\ufe0f",
[":woman_playing_water_polo_tone3:"] = "\U0001f93d\U0001f3fd\u200d\u2640\ufe0f",
[":woman_playing_water_polo_medium_skin_tone:"] = "\U0001f93d\U0001f3fd\u200d\u2640\ufe0f",
[":woman_playing_water_polo::skin-tone-3:"] = "\U0001f93d\U0001f3fd\u200d\u2640\ufe0f",
[":woman_playing_water_polo_tone4:"] = "\U0001f93d\U0001f3fe\u200d\u2640\ufe0f",
[":woman_playing_water_polo_medium_dark_skin_tone:"] = "\U0001f93d\U0001f3fe\u200d\u2640\ufe0f",
[":woman_playing_water_polo::skin-tone-4:"] = "\U0001f93d\U0001f3fe\u200d\u2640\ufe0f",
[":woman_playing_water_polo_tone5:"] = "\U0001f93d\U0001f3ff\u200d\u2640\ufe0f",
[":woman_playing_water_polo_dark_skin_tone:"] = "\U0001f93d\U0001f3ff\u200d\u2640\ufe0f",
[":woman_playing_water_polo::skin-tone-5:"] = "\U0001f93d\U0001f3ff\u200d\u2640\ufe0f",
[":woman_police_officer:"] = "\U0001f46e\u200d\u2640\ufe0f",
[":woman_police_officer_tone1:"] = "\U0001f46e\U0001f3fb\u200d\u2640\ufe0f",
[":woman_police_officer_light_skin_tone:"] = "\U0001f46e\U0001f3fb\u200d\u2640\ufe0f",
[":woman_police_officer::skin-tone-1:"] = "\U0001f46e\U0001f3fb\u200d\u2640\ufe0f",
[":woman_police_officer_tone2:"] = "\U0001f46e\U0001f3fc\u200d\u2640\ufe0f",
[":woman_police_officer_medium_light_skin_tone:"] = "\U0001f46e\U0001f3fc\u200d\u2640\ufe0f",
[":woman_police_officer::skin-tone-2:"] = "\U0001f46e\U0001f3fc\u200d\u2640\ufe0f",
[":woman_police_officer_tone3:"] = "\U0001f46e\U0001f3fd\u200d\u2640\ufe0f",
[":woman_police_officer_medium_skin_tone:"] = "\U0001f46e\U0001f3fd\u200d\u2640\ufe0f",
[":woman_police_officer::skin-tone-3:"] = "\U0001f46e\U0001f3fd\u200d\u2640\ufe0f",
[":woman_police_officer_tone4:"] = "\U0001f46e\U0001f3fe\u200d\u2640\ufe0f",
[":woman_police_officer_medium_dark_skin_tone:"] = "\U0001f46e\U0001f3fe\u200d\u2640\ufe0f",
[":woman_police_officer::skin-tone-4:"] = "\U0001f46e\U0001f3fe\u200d\u2640\ufe0f",
[":woman_police_officer_tone5:"] = "\U0001f46e\U0001f3ff\u200d\u2640\ufe0f",
[":woman_police_officer_dark_skin_tone:"] = "\U0001f46e\U0001f3ff\u200d\u2640\ufe0f",
[":woman_police_officer::skin-tone-5:"] = "\U0001f46e\U0001f3ff\u200d\u2640\ufe0f",
[":woman_pouting:"] = "\U0001f64e\u200d\u2640\ufe0f",
[":woman_pouting_tone1:"] = "\U0001f64e\U0001f3fb\u200d\u2640\ufe0f",
[":woman_pouting_light_skin_tone:"] = "\U0001f64e\U0001f3fb\u200d\u2640\ufe0f",
[":woman_pouting::skin-tone-1:"] = "\U0001f64e\U0001f3fb\u200d\u2640\ufe0f",
[":woman_pouting_tone2:"] = "\U0001f64e\U0001f3fc\u200d\u2640\ufe0f",
[":woman_pouting_medium_light_skin_tone:"] = "\U0001f64e\U0001f3fc\u200d\u2640\ufe0f",
[":woman_pouting::skin-tone-2:"] = "\U0001f64e\U0001f3fc\u200d\u2640\ufe0f",
[":woman_pouting_tone3:"] = "\U0001f64e\U0001f3fd\u200d\u2640\ufe0f",
[":woman_pouting_medium_skin_tone:"] = "\U0001f64e\U0001f3fd\u200d\u2640\ufe0f",
[":woman_pouting::skin-tone-3:"] = "\U0001f64e\U0001f3fd\u200d\u2640\ufe0f",
[":woman_pouting_tone4:"] = "\U0001f64e\U0001f3fe\u200d\u2640\ufe0f",
[":woman_pouting_medium_dark_skin_tone:"] = "\U0001f64e\U0001f3fe\u200d\u2640\ufe0f",
[":woman_pouting::skin-tone-4:"] = "\U0001f64e\U0001f3fe\u200d\u2640\ufe0f",
[":woman_pouting_tone5:"] = "\U0001f64e\U0001f3ff\u200d\u2640\ufe0f",
[":woman_pouting_dark_skin_tone:"] = "\U0001f64e\U0001f3ff\u200d\u2640\ufe0f",
[":woman_pouting::skin-tone-5:"] = "\U0001f64e\U0001f3ff\u200d\u2640\ufe0f",
[":woman_raising_hand:"] = "\U0001f64b\u200d\u2640\ufe0f",
[":woman_raising_hand_tone1:"] = "\U0001f64b\U0001f3fb\u200d\u2640\ufe0f",
[":woman_raising_hand_light_skin_tone:"] = "\U0001f64b\U0001f3fb\u200d\u2640\ufe0f",
[":woman_raising_hand::skin-tone-1:"] = "\U0001f64b\U0001f3fb\u200d\u2640\ufe0f",
[":woman_raising_hand_tone2:"] = "\U0001f64b\U0001f3fc\u200d\u2640\ufe0f",
[":woman_raising_hand_medium_light_skin_tone:"] = "\U0001f64b\U0001f3fc\u200d\u2640\ufe0f",
[":woman_raising_hand::skin-tone-2:"] = "\U0001f64b\U0001f3fc\u200d\u2640\ufe0f",
[":woman_raising_hand_tone3:"] = "\U0001f64b\U0001f3fd\u200d\u2640\ufe0f",
[":woman_raising_hand_medium_skin_tone:"] = "\U0001f64b\U0001f3fd\u200d\u2640\ufe0f",
[":woman_raising_hand::skin-tone-3:"] = "\U0001f64b\U0001f3fd\u200d\u2640\ufe0f",
[":woman_raising_hand_tone4:"] = "\U0001f64b\U0001f3fe\u200d\u2640\ufe0f",
[":woman_raising_hand_medium_dark_skin_tone:"] = "\U0001f64b\U0001f3fe\u200d\u2640\ufe0f",
[":woman_raising_hand::skin-tone-4:"] = "\U0001f64b\U0001f3fe\u200d\u2640\ufe0f",
[":woman_raising_hand_tone5:"] = "\U0001f64b\U0001f3ff\u200d\u2640\ufe0f",
[":woman_raising_hand_dark_skin_tone:"] = "\U0001f64b\U0001f3ff\u200d\u2640\ufe0f",
[":woman_raising_hand::skin-tone-5:"] = "\U0001f64b\U0001f3ff\u200d\u2640\ufe0f",
[":woman_red_haired:"] = "\U0001f469\u200d\U0001f9b0",
[":woman_red_haired_tone1:"] = "\U0001f469\U0001f3fb\u200d\U0001f9b0",
[":woman_red_haired_light_skin_tone:"] = "\U0001f469\U0001f3fb\u200d\U0001f9b0",
[":woman_red_haired::skin-tone-1:"] = "\U0001f469\U0001f3fb\u200d\U0001f9b0",
[":woman_red_haired_tone2:"] = "\U0001f469\U0001f3fc\u200d\U0001f9b0",
[":woman_red_haired_medium_light_skin_tone:"] = "\U0001f469\U0001f3fc\u200d\U0001f9b0",
[":woman_red_haired::skin-tone-2:"] = "\U0001f469\U0001f3fc\u200d\U0001f9b0",
[":woman_red_haired_tone3:"] = "\U0001f469\U0001f3fd\u200d\U0001f9b0",
[":woman_red_haired_medium_skin_tone:"] = "\U0001f469\U0001f3fd\u200d\U0001f9b0",
[":woman_red_haired::skin-tone-3:"] = "\U0001f469\U0001f3fd\u200d\U0001f9b0",
[":woman_red_haired_tone4:"] = "\U0001f469\U0001f3fe\u200d\U0001f9b0",
[":woman_red_haired_medium_dark_skin_tone:"] = "\U0001f469\U0001f3fe\u200d\U0001f9b0",
[":woman_red_haired::skin-tone-4:"] = "\U0001f469\U0001f3fe\u200d\U0001f9b0",
[":woman_red_haired_tone5:"] = "\U0001f469\U0001f3ff\u200d\U0001f9b0",
[":woman_red_haired_dark_skin_tone:"] = "\U0001f469\U0001f3ff\u200d\U0001f9b0",
[":woman_red_haired::skin-tone-5:"] = "\U0001f469\U0001f3ff\u200d\U0001f9b0",
[":woman_rowing_boat:"] = "\U0001f6a3\u200d\u2640\ufe0f",
[":woman_rowing_boat_tone1:"] = "\U0001f6a3\U0001f3fb\u200d\u2640\ufe0f",
[":woman_rowing_boat_light_skin_tone:"] = "\U0001f6a3\U0001f3fb\u200d\u2640\ufe0f",
[":woman_rowing_boat::skin-tone-1:"] = "\U0001f6a3\U0001f3fb\u200d\u2640\ufe0f",
[":woman_rowing_boat_tone2:"] = "\U0001f6a3\U0001f3fc\u200d\u2640\ufe0f",
[":woman_rowing_boat_medium_light_skin_tone:"] = "\U0001f6a3\U0001f3fc\u200d\u2640\ufe0f",
[":woman_rowing_boat::skin-tone-2:"] = "\U0001f6a3\U0001f3fc\u200d\u2640\ufe0f",
[":woman_rowing_boat_tone3:"] = "\U0001f6a3\U0001f3fd\u200d\u2640\ufe0f",
[":woman_rowing_boat_medium_skin_tone:"] = "\U0001f6a3\U0001f3fd\u200d\u2640\ufe0f",
[":woman_rowing_boat::skin-tone-3:"] = "\U0001f6a3\U0001f3fd\u200d\u2640\ufe0f",
[":woman_rowing_boat_tone4:"] = "\U0001f6a3\U0001f3fe\u200d\u2640\ufe0f",
[":woman_rowing_boat_medium_dark_skin_tone:"] = "\U0001f6a3\U0001f3fe\u200d\u2640\ufe0f",
[":woman_rowing_boat::skin-tone-4:"] = "\U0001f6a3\U0001f3fe\u200d\u2640\ufe0f",
[":woman_rowing_boat_tone5:"] = "\U0001f6a3\U0001f3ff\u200d\u2640\ufe0f",
[":woman_rowing_boat_dark_skin_tone:"] = "\U0001f6a3\U0001f3ff\u200d\u2640\ufe0f",
[":woman_rowing_boat::skin-tone-5:"] = "\U0001f6a3\U0001f3ff\u200d\u2640\ufe0f",
[":woman_running:"] = "\U0001f3c3\u200d\u2640\ufe0f",
[":woman_running_tone1:"] = "\U0001f3c3\U0001f3fb\u200d\u2640\ufe0f",
[":woman_running_light_skin_tone:"] = "\U0001f3c3\U0001f3fb\u200d\u2640\ufe0f",
[":woman_running::skin-tone-1:"] = "\U0001f3c3\U0001f3fb\u200d\u2640\ufe0f",
[":woman_running_tone2:"] = "\U0001f3c3\U0001f3fc\u200d\u2640\ufe0f",
[":woman_running_medium_light_skin_tone:"] = "\U0001f3c3\U0001f3fc\u200d\u2640\ufe0f",
[":woman_running::skin-tone-2:"] = "\U0001f3c3\U0001f3fc\u200d\u2640\ufe0f",
[":woman_running_tone3:"] = "\U0001f3c3\U0001f3fd\u200d\u2640\ufe0f",
[":woman_running_medium_skin_tone:"] = "\U0001f3c3\U0001f3fd\u200d\u2640\ufe0f",
[":woman_running::skin-tone-3:"] = "\U0001f3c3\U0001f3fd\u200d\u2640\ufe0f",
[":woman_running_tone4:"] = "\U0001f3c3\U0001f3fe\u200d\u2640\ufe0f",
[":woman_running_medium_dark_skin_tone:"] = "\U0001f3c3\U0001f3fe\u200d\u2640\ufe0f",
[":woman_running::skin-tone-4:"] = "\U0001f3c3\U0001f3fe\u200d\u2640\ufe0f",
[":woman_running_tone5:"] = "\U0001f3c3\U0001f3ff\u200d\u2640\ufe0f",
[":woman_running_dark_skin_tone:"] = "\U0001f3c3\U0001f3ff\u200d\u2640\ufe0f",
[":woman_running::skin-tone-5:"] = "\U0001f3c3\U0001f3ff\u200d\u2640\ufe0f",
[":woman_scientist:"] = "\U0001f469\u200d\U0001f52c",
[":woman_scientist_tone1:"] = "\U0001f469\U0001f3fb\u200d\U0001f52c",
[":woman_scientist_light_skin_tone:"] = "\U0001f469\U0001f3fb\u200d\U0001f52c",
[":woman_scientist::skin-tone-1:"] = "\U0001f469\U0001f3fb\u200d\U0001f52c",
[":woman_scientist_tone2:"] = "\U0001f469\U0001f3fc\u200d\U0001f52c",
[":woman_scientist_medium_light_skin_tone:"] = "\U0001f469\U0001f3fc\u200d\U0001f52c",
[":woman_scientist::skin-tone-2:"] = "\U0001f469\U0001f3fc\u200d\U0001f52c",
[":woman_scientist_tone3:"] = "\U0001f469\U0001f3fd\u200d\U0001f52c",
[":woman_scientist_medium_skin_tone:"] = "\U0001f469\U0001f3fd\u200d\U0001f52c",
[":woman_scientist::skin-tone-3:"] = "\U0001f469\U0001f3fd\u200d\U0001f52c",
[":woman_scientist_tone4:"] = "\U0001f469\U0001f3fe\u200d\U0001f52c",
[":woman_scientist_medium_dark_skin_tone:"] = "\U0001f469\U0001f3fe\u200d\U0001f52c",
[":woman_scientist::skin-tone-4:"] = "\U0001f469\U0001f3fe\u200d\U0001f52c",
[":woman_scientist_tone5:"] = "\U0001f469\U0001f3ff\u200d\U0001f52c",
[":woman_scientist_dark_skin_tone:"] = "\U0001f469\U0001f3ff\u200d\U0001f52c",
[":woman_scientist::skin-tone-5:"] = "\U0001f469\U0001f3ff\u200d\U0001f52c",
[":woman_shrugging:"] = "\U0001f937\u200d\u2640\ufe0f",
[":woman_shrugging_tone1:"] = "\U0001f937\U0001f3fb\u200d\u2640\ufe0f",
[":woman_shrugging_light_skin_tone:"] = "\U0001f937\U0001f3fb\u200d\u2640\ufe0f",
[":woman_shrugging::skin-tone-1:"] = "\U0001f937\U0001f3fb\u200d\u2640\ufe0f",
[":woman_shrugging_tone2:"] = "\U0001f937\U0001f3fc\u200d\u2640\ufe0f",
[":woman_shrugging_medium_light_skin_tone:"] = "\U0001f937\U0001f3fc\u200d\u2640\ufe0f",
[":woman_shrugging::skin-tone-2:"] = "\U0001f937\U0001f3fc\u200d\u2640\ufe0f",
[":woman_shrugging_tone3:"] = "\U0001f937\U0001f3fd\u200d\u2640\ufe0f",
[":woman_shrugging_medium_skin_tone:"] = "\U0001f937\U0001f3fd\u200d\u2640\ufe0f",
[":woman_shrugging::skin-tone-3:"] = "\U0001f937\U0001f3fd\u200d\u2640\ufe0f",
[":woman_shrugging_tone4:"] = "\U0001f937\U0001f3fe\u200d\u2640\ufe0f",
[":woman_shrugging_medium_dark_skin_tone:"] = "\U0001f937\U0001f3fe\u200d\u2640\ufe0f",
[":woman_shrugging::skin-tone-4:"] = "\U0001f937\U0001f3fe\u200d\u2640\ufe0f",
[":woman_shrugging_tone5:"] = "\U0001f937\U0001f3ff\u200d\u2640\ufe0f",
[":woman_shrugging_dark_skin_tone:"] = "\U0001f937\U0001f3ff\u200d\u2640\ufe0f",
[":woman_shrugging::skin-tone-5:"] = "\U0001f937\U0001f3ff\u200d\u2640\ufe0f",
[":woman_singer:"] = "\U0001f469\u200d\U0001f3a4",
[":woman_singer_tone1:"] = "\U0001f469\U0001f3fb\u200d\U0001f3a4",
[":woman_singer_light_skin_tone:"] = "\U0001f469\U0001f3fb\u200d\U0001f3a4",
[":woman_singer::skin-tone-1:"] = "\U0001f469\U0001f3fb\u200d\U0001f3a4",
[":woman_singer_tone2:"] = "\U0001f469\U0001f3fc\u200d\U0001f3a4",
[":woman_singer_medium_light_skin_tone:"] = "\U0001f469\U0001f3fc\u200d\U0001f3a4",
[":woman_singer::skin-tone-2:"] = "\U0001f469\U0001f3fc\u200d\U0001f3a4",
[":woman_singer_tone3:"] = "\U0001f469\U0001f3fd\u200d\U0001f3a4",
[":woman_singer_medium_skin_tone:"] = "\U0001f469\U0001f3fd\u200d\U0001f3a4",
[":woman_singer::skin-tone-3:"] = "\U0001f469\U0001f3fd\u200d\U0001f3a4",
[":woman_singer_tone4:"] = "\U0001f469\U0001f3fe\u200d\U0001f3a4",
[":woman_singer_medium_dark_skin_tone:"] = "\U0001f469\U0001f3fe\u200d\U0001f3a4",
[":woman_singer::skin-tone-4:"] = "\U0001f469\U0001f3fe\u200d\U0001f3a4",
[":woman_singer_tone5:"] = "\U0001f469\U0001f3ff\u200d\U0001f3a4",
[":woman_singer_dark_skin_tone:"] = "\U0001f469\U0001f3ff\u200d\U0001f3a4",
[":woman_singer::skin-tone-5:"] = "\U0001f469\U0001f3ff\u200d\U0001f3a4",
[":woman_standing:"] = "\U0001f9cd\u200d\u2640\ufe0f",
[":woman_standing_tone1:"] = "\U0001f9cd\U0001f3fb\u200d\u2640\ufe0f",
[":woman_standing_light_skin_tone:"] = "\U0001f9cd\U0001f3fb\u200d\u2640\ufe0f",
[":woman_standing::skin-tone-1:"] = "\U0001f9cd\U0001f3fb\u200d\u2640\ufe0f",
[":woman_standing_tone2:"] = "\U0001f9cd\U0001f3fc\u200d\u2640\ufe0f",
[":woman_standing_medium_light_skin_tone:"] = "\U0001f9cd\U0001f3fc\u200d\u2640\ufe0f",
[":woman_standing::skin-tone-2:"] = "\U0001f9cd\U0001f3fc\u200d\u2640\ufe0f",
[":woman_standing_tone3:"] = "\U0001f9cd\U0001f3fd\u200d\u2640\ufe0f",
[":woman_standing_medium_skin_tone:"] = "\U0001f9cd\U0001f3fd\u200d\u2640\ufe0f",
[":woman_standing::skin-tone-3:"] = "\U0001f9cd\U0001f3fd\u200d\u2640\ufe0f",
[":woman_standing_tone4:"] = "\U0001f9cd\U0001f3fe\u200d\u2640\ufe0f",
[":woman_standing_medium_dark_skin_tone:"] = "\U0001f9cd\U0001f3fe\u200d\u2640\ufe0f",
[":woman_standing::skin-tone-4:"] = "\U0001f9cd\U0001f3fe\u200d\u2640\ufe0f",
[":woman_standing_tone5:"] = "\U0001f9cd\U0001f3ff\u200d\u2640\ufe0f",
[":woman_standing_dark_skin_tone:"] = "\U0001f9cd\U0001f3ff\u200d\u2640\ufe0f",
[":woman_standing::skin-tone-5:"] = "\U0001f9cd\U0001f3ff\u200d\u2640\ufe0f",
[":woman_student:"] = "\U0001f469\u200d\U0001f393",
[":woman_student_tone1:"] = "\U0001f469\U0001f3fb\u200d\U0001f393",
[":woman_student_light_skin_tone:"] = "\U0001f469\U0001f3fb\u200d\U0001f393",
[":woman_student::skin-tone-1:"] = "\U0001f469\U0001f3fb\u200d\U0001f393",
[":woman_student_tone2:"] = "\U0001f469\U0001f3fc\u200d\U0001f393",
[":woman_student_medium_light_skin_tone:"] = "\U0001f469\U0001f3fc\u200d\U0001f393",
[":woman_student::skin-tone-2:"] = "\U0001f469\U0001f3fc\u200d\U0001f393",
[":woman_student_tone3:"] = "\U0001f469\U0001f3fd\u200d\U0001f393",
[":woman_student_medium_skin_tone:"] = "\U0001f469\U0001f3fd\u200d\U0001f393",
[":woman_student::skin-tone-3:"] = "\U0001f469\U0001f3fd\u200d\U0001f393",
[":woman_student_tone4:"] = "\U0001f469\U0001f3fe\u200d\U0001f393",
[":woman_student_medium_dark_skin_tone:"] = "\U0001f469\U0001f3fe\u200d\U0001f393",
[":woman_student::skin-tone-4:"] = "\U0001f469\U0001f3fe\u200d\U0001f393",
[":woman_student_tone5:"] = "\U0001f469\U0001f3ff\u200d\U0001f393",
[":woman_student_dark_skin_tone:"] = "\U0001f469\U0001f3ff\u200d\U0001f393",
[":woman_student::skin-tone-5:"] = "\U0001f469\U0001f3ff\u200d\U0001f393",
[":woman_superhero:"] = "\U0001f9b8\u200d\u2640\ufe0f",
[":woman_superhero_tone1:"] = "\U0001f9b8\U0001f3fb\u200d\u2640\ufe0f",
[":woman_superhero_light_skin_tone:"] = "\U0001f9b8\U0001f3fb\u200d\u2640\ufe0f",
[":woman_superhero::skin-tone-1:"] = "\U0001f9b8\U0001f3fb\u200d\u2640\ufe0f",
[":woman_superhero_tone2:"] = "\U0001f9b8\U0001f3fc\u200d\u2640\ufe0f",
[":woman_superhero_medium_light_skin_tone:"] = "\U0001f9b8\U0001f3fc\u200d\u2640\ufe0f",
[":woman_superhero::skin-tone-2:"] = "\U0001f9b8\U0001f3fc\u200d\u2640\ufe0f",
[":woman_superhero_tone3:"] = "\U0001f9b8\U0001f3fd\u200d\u2640\ufe0f",
[":woman_superhero_medium_skin_tone:"] = "\U0001f9b8\U0001f3fd\u200d\u2640\ufe0f",
[":woman_superhero::skin-tone-3:"] = "\U0001f9b8\U0001f3fd\u200d\u2640\ufe0f",
[":woman_superhero_tone4:"] = "\U0001f9b8\U0001f3fe\u200d\u2640\ufe0f",
[":woman_superhero_medium_dark_skin_tone:"] = "\U0001f9b8\U0001f3fe\u200d\u2640\ufe0f",
[":woman_superhero::skin-tone-4:"] = "\U0001f9b8\U0001f3fe\u200d\u2640\ufe0f",
[":woman_superhero_tone5:"] = "\U0001f9b8\U0001f3ff\u200d\u2640\ufe0f",
[":woman_superhero_dark_skin_tone:"] = "\U0001f9b8\U0001f3ff\u200d\u2640\ufe0f",
[":woman_superhero::skin-tone-5:"] = "\U0001f9b8\U0001f3ff\u200d\u2640\ufe0f",
[":woman_supervillain:"] = "\U0001f9b9\u200d\u2640\ufe0f",
[":woman_supervillain_tone1:"] = "\U0001f9b9\U0001f3fb\u200d\u2640\ufe0f",
[":woman_supervillain_light_skin_tone:"] = "\U0001f9b9\U0001f3fb\u200d\u2640\ufe0f",
[":woman_supervillain::skin-tone-1:"] = "\U0001f9b9\U0001f3fb\u200d\u2640\ufe0f",
[":woman_supervillain_tone2:"] = "\U0001f9b9\U0001f3fc\u200d\u2640\ufe0f",
[":woman_supervillain_medium_light_skin_tone:"] = "\U0001f9b9\U0001f3fc\u200d\u2640\ufe0f",
[":woman_supervillain::skin-tone-2:"] = "\U0001f9b9\U0001f3fc\u200d\u2640\ufe0f",
[":woman_supervillain_tone3:"] = "\U0001f9b9\U0001f3fd\u200d\u2640\ufe0f",
[":woman_supervillain_medium_skin_tone:"] = "\U0001f9b9\U0001f3fd\u200d\u2640\ufe0f",
[":woman_supervillain::skin-tone-3:"] = "\U0001f9b9\U0001f3fd\u200d\u2640\ufe0f",
[":woman_supervillain_tone4:"] = "\U0001f9b9\U0001f3fe\u200d\u2640\ufe0f",
[":woman_supervillain_medium_dark_skin_tone:"] = "\U0001f9b9\U0001f3fe\u200d\u2640\ufe0f",
[":woman_supervillain::skin-tone-4:"] = "\U0001f9b9\U0001f3fe\u200d\u2640\ufe0f",
[":woman_supervillain_tone5:"] = "\U0001f9b9\U0001f3ff\u200d\u2640\ufe0f",
[":woman_supervillain_dark_skin_tone:"] = "\U0001f9b9\U0001f3ff\u200d\u2640\ufe0f",
[":woman_supervillain::skin-tone-5:"] = "\U0001f9b9\U0001f3ff\u200d\u2640\ufe0f",
[":woman_surfing:"] = "\U0001f3c4\u200d\u2640\ufe0f",
[":woman_surfing_tone1:"] = "\U0001f3c4\U0001f3fb\u200d\u2640\ufe0f",
[":woman_surfing_light_skin_tone:"] = "\U0001f3c4\U0001f3fb\u200d\u2640\ufe0f",
[":woman_surfing::skin-tone-1:"] = "\U0001f3c4\U0001f3fb\u200d\u2640\ufe0f",
[":woman_surfing_tone2:"] = "\U0001f3c4\U0001f3fc\u200d\u2640\ufe0f",
[":woman_surfing_medium_light_skin_tone:"] = "\U0001f3c4\U0001f3fc\u200d\u2640\ufe0f",
[":woman_surfing::skin-tone-2:"] = "\U0001f3c4\U0001f3fc\u200d\u2640\ufe0f",
[":woman_surfing_tone3:"] = "\U0001f3c4\U0001f3fd\u200d\u2640\ufe0f",
[":woman_surfing_medium_skin_tone:"] = "\U0001f3c4\U0001f3fd\u200d\u2640\ufe0f",
[":woman_surfing::skin-tone-3:"] = "\U0001f3c4\U0001f3fd\u200d\u2640\ufe0f",
[":woman_surfing_tone4:"] = "\U0001f3c4\U0001f3fe\u200d\u2640\ufe0f",
[":woman_surfing_medium_dark_skin_tone:"] = "\U0001f3c4\U0001f3fe\u200d\u2640\ufe0f",
[":woman_surfing::skin-tone-4:"] = "\U0001f3c4\U0001f3fe\u200d\u2640\ufe0f",
[":woman_surfing_tone5:"] = "\U0001f3c4\U0001f3ff\u200d\u2640\ufe0f",
[":woman_surfing_dark_skin_tone:"] = "\U0001f3c4\U0001f3ff\u200d\u2640\ufe0f",
[":woman_surfing::skin-tone-5:"] = "\U0001f3c4\U0001f3ff\u200d\u2640\ufe0f",
[":woman_swimming:"] = "\U0001f3ca\u200d\u2640\ufe0f",
[":woman_swimming_tone1:"] = "\U0001f3ca\U0001f3fb\u200d\u2640\ufe0f",
[":woman_swimming_light_skin_tone:"] = "\U0001f3ca\U0001f3fb\u200d\u2640\ufe0f",
[":woman_swimming::skin-tone-1:"] = "\U0001f3ca\U0001f3fb\u200d\u2640\ufe0f",
[":woman_swimming_tone2:"] = "\U0001f3ca\U0001f3fc\u200d\u2640\ufe0f",
[":woman_swimming_medium_light_skin_tone:"] = "\U0001f3ca\U0001f3fc\u200d\u2640\ufe0f",
[":woman_swimming::skin-tone-2:"] = "\U0001f3ca\U0001f3fc\u200d\u2640\ufe0f",
[":woman_swimming_tone3:"] = "\U0001f3ca\U0001f3fd\u200d\u2640\ufe0f",
[":woman_swimming_medium_skin_tone:"] = "\U0001f3ca\U0001f3fd\u200d\u2640\ufe0f",
[":woman_swimming::skin-tone-3:"] = "\U0001f3ca\U0001f3fd\u200d\u2640\ufe0f",
[":woman_swimming_tone4:"] = "\U0001f3ca\U0001f3fe\u200d\u2640\ufe0f",
[":woman_swimming_medium_dark_skin_tone:"] = "\U0001f3ca\U0001f3fe\u200d\u2640\ufe0f",
[":woman_swimming::skin-tone-4:"] = "\U0001f3ca\U0001f3fe\u200d\u2640\ufe0f",
[":woman_swimming_tone5:"] = "\U0001f3ca\U0001f3ff\u200d\u2640\ufe0f",
[":woman_swimming_dark_skin_tone:"] = "\U0001f3ca\U0001f3ff\u200d\u2640\ufe0f",
[":woman_swimming::skin-tone-5:"] = "\U0001f3ca\U0001f3ff\u200d\u2640\ufe0f",
[":woman_teacher:"] = "\U0001f469\u200d\U0001f3eb",
[":woman_teacher_tone1:"] = "\U0001f469\U0001f3fb\u200d\U0001f3eb",
[":woman_teacher_light_skin_tone:"] = "\U0001f469\U0001f3fb\u200d\U0001f3eb",
[":woman_teacher::skin-tone-1:"] = "\U0001f469\U0001f3fb\u200d\U0001f3eb",
[":woman_teacher_tone2:"] = "\U0001f469\U0001f3fc\u200d\U0001f3eb",
[":woman_teacher_medium_light_skin_tone:"] = "\U0001f469\U0001f3fc\u200d\U0001f3eb",
[":woman_teacher::skin-tone-2:"] = "\U0001f469\U0001f3fc\u200d\U0001f3eb",
[":woman_teacher_tone3:"] = "\U0001f469\U0001f3fd\u200d\U0001f3eb",
[":woman_teacher_medium_skin_tone:"] = "\U0001f469\U0001f3fd\u200d\U0001f3eb",
[":woman_teacher::skin-tone-3:"] = "\U0001f469\U0001f3fd\u200d\U0001f3eb",
[":woman_teacher_tone4:"] = "\U0001f469\U0001f3fe\u200d\U0001f3eb",
[":woman_teacher_medium_dark_skin_tone:"] = "\U0001f469\U0001f3fe\u200d\U0001f3eb",
[":woman_teacher::skin-tone-4:"] = "\U0001f469\U0001f3fe\u200d\U0001f3eb",
[":woman_teacher_tone5:"] = "\U0001f469\U0001f3ff\u200d\U0001f3eb",
[":woman_teacher_dark_skin_tone:"] = "\U0001f469\U0001f3ff\u200d\U0001f3eb",
[":woman_teacher::skin-tone-5:"] = "\U0001f469\U0001f3ff\u200d\U0001f3eb",
[":woman_technologist:"] = "\U0001f469\u200d\U0001f4bb",
[":woman_technologist_tone1:"] = "\U0001f469\U0001f3fb\u200d\U0001f4bb",
[":woman_technologist_light_skin_tone:"] = "\U0001f469\U0001f3fb\u200d\U0001f4bb",
[":woman_technologist::skin-tone-1:"] = "\U0001f469\U0001f3fb\u200d\U0001f4bb",
[":woman_technologist_tone2:"] = "\U0001f469\U0001f3fc\u200d\U0001f4bb",
[":woman_technologist_medium_light_skin_tone:"] = "\U0001f469\U0001f3fc\u200d\U0001f4bb",
[":woman_technologist::skin-tone-2:"] = "\U0001f469\U0001f3fc\u200d\U0001f4bb",
[":woman_technologist_tone3:"] = "\U0001f469\U0001f3fd\u200d\U0001f4bb",
[":woman_technologist_medium_skin_tone:"] = "\U0001f469\U0001f3fd\u200d\U0001f4bb",
[":woman_technologist::skin-tone-3:"] = "\U0001f469\U0001f3fd\u200d\U0001f4bb",
[":woman_technologist_tone4:"] = "\U0001f469\U0001f3fe\u200d\U0001f4bb",
[":woman_technologist_medium_dark_skin_tone:"] = "\U0001f469\U0001f3fe\u200d\U0001f4bb",
[":woman_technologist::skin-tone-4:"] = "\U0001f469\U0001f3fe\u200d\U0001f4bb",
[":woman_technologist_tone5:"] = "\U0001f469\U0001f3ff\u200d\U0001f4bb",
[":woman_technologist_dark_skin_tone:"] = "\U0001f469\U0001f3ff\u200d\U0001f4bb",
[":woman_technologist::skin-tone-5:"] = "\U0001f469\U0001f3ff\u200d\U0001f4bb",
[":woman_tipping_hand:"] = "\U0001f481\u200d\u2640\ufe0f",
[":woman_tipping_hand_tone1:"] = "\U0001f481\U0001f3fb\u200d\u2640\ufe0f",
[":woman_tipping_hand_light_skin_tone:"] = "\U0001f481\U0001f3fb\u200d\u2640\ufe0f",
[":woman_tipping_hand::skin-tone-1:"] = "\U0001f481\U0001f3fb\u200d\u2640\ufe0f",
[":woman_tipping_hand_tone2:"] = "\U0001f481\U0001f3fc\u200d\u2640\ufe0f",
[":woman_tipping_hand_medium_light_skin_tone:"] = "\U0001f481\U0001f3fc\u200d\u2640\ufe0f",
[":woman_tipping_hand::skin-tone-2:"] = "\U0001f481\U0001f3fc\u200d\u2640\ufe0f",
[":woman_tipping_hand_tone3:"] = "\U0001f481\U0001f3fd\u200d\u2640\ufe0f",
[":woman_tipping_hand_medium_skin_tone:"] = "\U0001f481\U0001f3fd\u200d\u2640\ufe0f",
[":woman_tipping_hand::skin-tone-3:"] = "\U0001f481\U0001f3fd\u200d\u2640\ufe0f",
[":woman_tipping_hand_tone4:"] = "\U0001f481\U0001f3fe\u200d\u2640\ufe0f",
[":woman_tipping_hand_medium_dark_skin_tone:"] = "\U0001f481\U0001f3fe\u200d\u2640\ufe0f",
[":woman_tipping_hand::skin-tone-4:"] = "\U0001f481\U0001f3fe\u200d\u2640\ufe0f",
[":woman_tipping_hand_tone5:"] = "\U0001f481\U0001f3ff\u200d\u2640\ufe0f",
[":woman_tipping_hand_dark_skin_tone:"] = "\U0001f481\U0001f3ff\u200d\u2640\ufe0f",
[":woman_tipping_hand::skin-tone-5:"] = "\U0001f481\U0001f3ff\u200d\u2640\ufe0f",
[":woman_tone1:"] = "\U0001f469\U0001f3fb",
[":woman::skin-tone-1:"] = "\U0001f469\U0001f3fb",
[":woman_tone2:"] = "\U0001f469\U0001f3fc",
[":woman::skin-tone-2:"] = "\U0001f469\U0001f3fc",
[":woman_tone3:"] = "\U0001f469\U0001f3fd",
[":woman::skin-tone-3:"] = "\U0001f469\U0001f3fd",
[":woman_tone4:"] = "\U0001f469\U0001f3fe",
[":woman::skin-tone-4:"] = "\U0001f469\U0001f3fe",
[":woman_tone5:"] = "\U0001f469\U0001f3ff",
[":woman::skin-tone-5:"] = "\U0001f469\U0001f3ff",
[":woman_vampire:"] = "\U0001f9db\u200d\u2640\ufe0f",
[":woman_vampire_tone1:"] = "\U0001f9db\U0001f3fb\u200d\u2640\ufe0f",
[":woman_vampire_light_skin_tone:"] = "\U0001f9db\U0001f3fb\u200d\u2640\ufe0f",
[":woman_vampire::skin-tone-1:"] = "\U0001f9db\U0001f3fb\u200d\u2640\ufe0f",
[":woman_vampire_tone2:"] = "\U0001f9db\U0001f3fc\u200d\u2640\ufe0f",
[":woman_vampire_medium_light_skin_tone:"] = "\U0001f9db\U0001f3fc\u200d\u2640\ufe0f",
[":woman_vampire::skin-tone-2:"] = "\U0001f9db\U0001f3fc\u200d\u2640\ufe0f",
[":woman_vampire_tone3:"] = "\U0001f9db\U0001f3fd\u200d\u2640\ufe0f",
[":woman_vampire_medium_skin_tone:"] = "\U0001f9db\U0001f3fd\u200d\u2640\ufe0f",
[":woman_vampire::skin-tone-3:"] = "\U0001f9db\U0001f3fd\u200d\u2640\ufe0f",
[":woman_vampire_tone4:"] = "\U0001f9db\U0001f3fe\u200d\u2640\ufe0f",
[":woman_vampire_medium_dark_skin_tone:"] = "\U0001f9db\U0001f3fe\u200d\u2640\ufe0f",
[":woman_vampire::skin-tone-4:"] = "\U0001f9db\U0001f3fe\u200d\u2640\ufe0f",
[":woman_vampire_tone5:"] = "\U0001f9db\U0001f3ff\u200d\u2640\ufe0f",
[":woman_vampire_dark_skin_tone:"] = "\U0001f9db\U0001f3ff\u200d\u2640\ufe0f",
[":woman_vampire::skin-tone-5:"] = "\U0001f9db\U0001f3ff\u200d\u2640\ufe0f",
[":woman_walking:"] = "\U0001f6b6\u200d\u2640\ufe0f",
[":woman_walking_tone1:"] = "\U0001f6b6\U0001f3fb\u200d\u2640\ufe0f",
[":woman_walking_light_skin_tone:"] = "\U0001f6b6\U0001f3fb\u200d\u2640\ufe0f",
[":woman_walking::skin-tone-1:"] = "\U0001f6b6\U0001f3fb\u200d\u2640\ufe0f",
[":woman_walking_tone2:"] = "\U0001f6b6\U0001f3fc\u200d\u2640\ufe0f",
[":woman_walking_medium_light_skin_tone:"] = "\U0001f6b6\U0001f3fc\u200d\u2640\ufe0f",
[":woman_walking::skin-tone-2:"] = "\U0001f6b6\U0001f3fc\u200d\u2640\ufe0f",
[":woman_walking_tone3:"] = "\U0001f6b6\U0001f3fd\u200d\u2640\ufe0f",
[":woman_walking_medium_skin_tone:"] = "\U0001f6b6\U0001f3fd\u200d\u2640\ufe0f",
[":woman_walking::skin-tone-3:"] = "\U0001f6b6\U0001f3fd\u200d\u2640\ufe0f",
[":woman_walking_tone4:"] = "\U0001f6b6\U0001f3fe\u200d\u2640\ufe0f",
[":woman_walking_medium_dark_skin_tone:"] = "\U0001f6b6\U0001f3fe\u200d\u2640\ufe0f",
[":woman_walking::skin-tone-4:"] = "\U0001f6b6\U0001f3fe\u200d\u2640\ufe0f",
[":woman_walking_tone5:"] = "\U0001f6b6\U0001f3ff\u200d\u2640\ufe0f",
[":woman_walking_dark_skin_tone:"] = "\U0001f6b6\U0001f3ff\u200d\u2640\ufe0f",
[":woman_walking::skin-tone-5:"] = "\U0001f6b6\U0001f3ff\u200d\u2640\ufe0f",
[":woman_wearing_turban:"] = "\U0001f473\u200d\u2640\ufe0f",
[":woman_wearing_turban_tone1:"] = "\U0001f473\U0001f3fb\u200d\u2640\ufe0f",
[":woman_wearing_turban_light_skin_tone:"] = "\U0001f473\U0001f3fb\u200d\u2640\ufe0f",
[":woman_wearing_turban::skin-tone-1:"] = "\U0001f473\U0001f3fb\u200d\u2640\ufe0f",
[":woman_wearing_turban_tone2:"] = "\U0001f473\U0001f3fc\u200d\u2640\ufe0f",
[":woman_wearing_turban_medium_light_skin_tone:"] = "\U0001f473\U0001f3fc\u200d\u2640\ufe0f",
[":woman_wearing_turban::skin-tone-2:"] = "\U0001f473\U0001f3fc\u200d\u2640\ufe0f",
[":woman_wearing_turban_tone3:"] = "\U0001f473\U0001f3fd\u200d\u2640\ufe0f",
[":woman_wearing_turban_medium_skin_tone:"] = "\U0001f473\U0001f3fd\u200d\u2640\ufe0f",
[":woman_wearing_turban::skin-tone-3:"] = "\U0001f473\U0001f3fd\u200d\u2640\ufe0f",
[":woman_wearing_turban_tone4:"] = "\U0001f473\U0001f3fe\u200d\u2640\ufe0f",
[":woman_wearing_turban_medium_dark_skin_tone:"] = "\U0001f473\U0001f3fe\u200d\u2640\ufe0f",
[":woman_wearing_turban::skin-tone-4:"] = "\U0001f473\U0001f3fe\u200d\u2640\ufe0f",
[":woman_wearing_turban_tone5:"] = "\U0001f473\U0001f3ff\u200d\u2640\ufe0f",
[":woman_wearing_turban_dark_skin_tone:"] = "\U0001f473\U0001f3ff\u200d\u2640\ufe0f",
[":woman_wearing_turban::skin-tone-5:"] = "\U0001f473\U0001f3ff\u200d\u2640\ufe0f",
[":woman_white_haired:"] = "\U0001f469\u200d\U0001f9b3",
[":woman_white_haired_tone1:"] = "\U0001f469\U0001f3fb\u200d\U0001f9b3",
[":woman_white_haired_light_skin_tone:"] = "\U0001f469\U0001f3fb\u200d\U0001f9b3",
[":woman_white_haired::skin-tone-1:"] = "\U0001f469\U0001f3fb\u200d\U0001f9b3",
[":woman_white_haired_tone2:"] = "\U0001f469\U0001f3fc\u200d\U0001f9b3",
[":woman_white_haired_medium_light_skin_tone:"] = "\U0001f469\U0001f3fc\u200d\U0001f9b3",
[":woman_white_haired::skin-tone-2:"] = "\U0001f469\U0001f3fc\u200d\U0001f9b3",
[":woman_white_haired_tone3:"] = "\U0001f469\U0001f3fd\u200d\U0001f9b3",
[":woman_white_haired_medium_skin_tone:"] = "\U0001f469\U0001f3fd\u200d\U0001f9b3",
[":woman_white_haired::skin-tone-3:"] = "\U0001f469\U0001f3fd\u200d\U0001f9b3",
[":woman_white_haired_tone4:"] = "\U0001f469\U0001f3fe\u200d\U0001f9b3",
[":woman_white_haired_medium_dark_skin_tone:"] = "\U0001f469\U0001f3fe\u200d\U0001f9b3",
[":woman_white_haired::skin-tone-4:"] = "\U0001f469\U0001f3fe\u200d\U0001f9b3",
[":woman_white_haired_tone5:"] = "\U0001f469\U0001f3ff\u200d\U0001f9b3",
[":woman_white_haired_dark_skin_tone:"] = "\U0001f469\U0001f3ff\u200d\U0001f9b3",
[":woman_white_haired::skin-tone-5:"] = "\U0001f469\U0001f3ff\u200d\U0001f9b3",
[":woman_with_headscarf:"] = "\U0001f9d5",
[":woman_with_headscarf_tone1:"] = "\U0001f9d5\U0001f3fb",
[":woman_with_headscarf_light_skin_tone:"] = "\U0001f9d5\U0001f3fb",
[":woman_with_headscarf::skin-tone-1:"] = "\U0001f9d5\U0001f3fb",
[":woman_with_headscarf_tone2:"] = "\U0001f9d5\U0001f3fc",
[":woman_with_headscarf_medium_light_skin_tone:"] = "\U0001f9d5\U0001f3fc",
[":woman_with_headscarf::skin-tone-2:"] = "\U0001f9d5\U0001f3fc",
[":woman_with_headscarf_tone3:"] = "\U0001f9d5\U0001f3fd",
[":woman_with_headscarf_medium_skin_tone:"] = "\U0001f9d5\U0001f3fd",
[":woman_with_headscarf::skin-tone-3:"] = "\U0001f9d5\U0001f3fd",
[":woman_with_headscarf_tone4:"] = "\U0001f9d5\U0001f3fe",
[":woman_with_headscarf_medium_dark_skin_tone:"] = "\U0001f9d5\U0001f3fe",
[":woman_with_headscarf::skin-tone-4:"] = "\U0001f9d5\U0001f3fe",
[":woman_with_headscarf_tone5:"] = "\U0001f9d5\U0001f3ff",
[":woman_with_headscarf_dark_skin_tone:"] = "\U0001f9d5\U0001f3ff",
[":woman_with_headscarf::skin-tone-5:"] = "\U0001f9d5\U0001f3ff",
[":woman_with_probing_cane:"] = "\U0001f469\u200d\U0001f9af",
[":woman_with_probing_cane_tone1:"] = "\U0001f469\U0001f3fb\u200d\U0001f9af",
[":woman_with_probing_cane_light_skin_tone:"] = "\U0001f469\U0001f3fb\u200d\U0001f9af",
[":woman_with_probing_cane::skin-tone-1:"] = "\U0001f469\U0001f3fb\u200d\U0001f9af",
[":woman_with_probing_cane_tone2:"] = "\U0001f469\U0001f3fc\u200d\U0001f9af",
[":woman_with_probing_cane_medium_light_skin_tone:"] = "\U0001f469\U0001f3fc\u200d\U0001f9af",
[":woman_with_probing_cane::skin-tone-2:"] = "\U0001f469\U0001f3fc\u200d\U0001f9af",
[":woman_with_probing_cane_tone3:"] = "\U0001f469\U0001f3fd\u200d\U0001f9af",
[":woman_with_probing_cane_medium_skin_tone:"] = "\U0001f469\U0001f3fd\u200d\U0001f9af",
[":woman_with_probing_cane::skin-tone-3:"] = "\U0001f469\U0001f3fd\u200d\U0001f9af",
[":woman_with_probing_cane_tone4:"] = "\U0001f469\U0001f3fe\u200d\U0001f9af",
[":woman_with_probing_cane_medium_dark_skin_tone:"] = "\U0001f469\U0001f3fe\u200d\U0001f9af",
[":woman_with_probing_cane::skin-tone-4:"] = "\U0001f469\U0001f3fe\u200d\U0001f9af",
[":woman_with_probing_cane_tone5:"] = "\U0001f469\U0001f3ff\u200d\U0001f9af",
[":woman_with_probing_cane_dark_skin_tone:"] = "\U0001f469\U0001f3ff\u200d\U0001f9af",
[":woman_with_probing_cane::skin-tone-5:"] = "\U0001f469\U0001f3ff\u200d\U0001f9af",
[":woman_with_veil:"] = "\U0001f470\u200d\u2640\ufe0f",
[":bride_with_veil:"] = "\U0001f470\u200d\u2640\ufe0f",
[":woman_with_veil_tone1:"] = "\U0001f470\U0001f3fb\u200d\u2640\ufe0f",
[":woman_with_veil_light_skin_tone:"] = "\U0001f470\U0001f3fb\u200d\u2640\ufe0f",
[":woman_with_veil::skin-tone-1:"] = "\U0001f470\U0001f3fb\u200d\u2640\ufe0f",
[":bride_with_veil::skin-tone-1:"] = "\U0001f470\U0001f3fb\u200d\u2640\ufe0f",
[":woman_with_veil_tone2:"] = "\U0001f470\U0001f3fc\u200d\u2640\ufe0f",
[":woman_with_veil_medium_light_skin_tone:"] = "\U0001f470\U0001f3fc\u200d\u2640\ufe0f",
[":woman_with_veil::skin-tone-2:"] = "\U0001f470\U0001f3fc\u200d\u2640\ufe0f",
[":bride_with_veil::skin-tone-2:"] = "\U0001f470\U0001f3fc\u200d\u2640\ufe0f",
[":woman_with_veil_tone3:"] = "\U0001f470\U0001f3fd\u200d\u2640\ufe0f",
[":woman_with_veil_medium_skin_tone:"] = "\U0001f470\U0001f3fd\u200d\u2640\ufe0f",
[":woman_with_veil::skin-tone-3:"] = "\U0001f470\U0001f3fd\u200d\u2640\ufe0f",
[":bride_with_veil::skin-tone-3:"] = "\U0001f470\U0001f3fd\u200d\u2640\ufe0f",
[":woman_with_veil_tone4:"] = "\U0001f470\U0001f3fe\u200d\u2640\ufe0f",
[":woman_with_veil_medium_dark_skin_tone:"] = "\U0001f470\U0001f3fe\u200d\u2640\ufe0f",
[":woman_with_veil::skin-tone-4:"] = "\U0001f470\U0001f3fe\u200d\u2640\ufe0f",
[":bride_with_veil::skin-tone-4:"] = "\U0001f470\U0001f3fe\u200d\u2640\ufe0f",
[":woman_with_veil_tone5:"] = "\U0001f470\U0001f3ff\u200d\u2640\ufe0f",
[":woman_with_veil_dark_skin_tone:"] = "\U0001f470\U0001f3ff\u200d\u2640\ufe0f",
[":woman_with_veil::skin-tone-5:"] = "\U0001f470\U0001f3ff\u200d\u2640\ufe0f",
[":bride_with_veil::skin-tone-5:"] = "\U0001f470\U0001f3ff\u200d\u2640\ufe0f",
[":woman_zombie:"] = "\U0001f9df\u200d\u2640\ufe0f",
[":womans_clothes:"] = "\U0001f45a",
[":womans_flat_shoe:"] = "\U0001f97f",
[":womans_hat:"] = "\U0001f452",
[":women_holding_hands_tone1:"] = "\U0001f46d",
[":women_holding_hands_light_skin_tone:"] = "\U0001f46d",
[":women_holding_hands_tone1_tone2:"] = "\U0001f46d",
[":women_holding_hands_light_skin_tone_medium_light_skin_tone:"] = "\U0001f46d",
[":women_holding_hands_tone1_tone3:"] = "\U0001f46d",
[":women_holding_hands_light_skin_tone_medium_skin_tone:"] = "\U0001f46d",
[":women_holding_hands_tone1_tone4:"] = "\U0001f46d",
[":women_holding_hands_light_skin_tone_medium_dark_skin_tone:"] = "\U0001f46d",
[":women_holding_hands_tone1_tone5:"] = "\U0001f46d",
[":women_holding_hands_light_skin_tone_dark_skin_tone:"] = "\U0001f46d",
[":women_holding_hands_tone2:"] = "\U0001f46d",
[":women_holding_hands_medium_light_skin_tone:"] = "\U0001f46d",
[":women_holding_hands_tone2_tone1:"] = "\U0001f46d",
[":women_holding_hands_medium_light_skin_tone_light_skin_tone:"] = "\U0001f46d",
[":women_holding_hands_tone2_tone3:"] = "\U0001f46d",
[":women_holding_hands_medium_light_skin_tone_medium_skin_tone:"] = "\U0001f46d",
[":women_holding_hands_tone2_tone4:"] = "\U0001f46d",
[":women_holding_hands_medium_light_skin_tone_medium_dark_skin_tone:"] = "\U0001f46d",
[":women_holding_hands_tone2_tone5:"] = "\U0001f46d",
[":women_holding_hands_medium_light_skin_tone_dark_skin_tone:"] = "\U0001f46d",
[":women_holding_hands_tone3:"] = "\U0001f46d",
[":women_holding_hands_medium_skin_tone:"] = "\U0001f46d",
[":women_holding_hands_tone3_tone1:"] = "\U0001f46d",
[":women_holding_hands_medium_skin_tone_light_skin_tone:"] = "\U0001f46d",
[":women_holding_hands_tone3_tone2:"] = "\U0001f46d",
[":women_holding_hands_medium_skin_tone_medium_light_skin_tone:"] = "\U0001f46d",
[":women_holding_hands_tone3_tone4:"] = "\U0001f46d",
[":women_holding_hands_medium_skin_tone_medium_dark_skin_tone:"] = "\U0001f46d",
[":women_holding_hands_tone3_tone5:"] = "\U0001f46d",
[":women_holding_hands_medium_skin_tone_dark_skin_tone:"] = "\U0001f46d",
[":women_holding_hands_tone4:"] = "\U0001f46d",
[":women_holding_hands_medium_dark_skin_tone:"] = "\U0001f46d",
[":women_holding_hands_tone4_tone1:"] = "\U0001f46d",
[":women_holding_hands_medium_dark_skin_tone_light_skin_tone:"] = "\U0001f46d",
[":women_holding_hands_tone4_tone2:"] = "\U0001f46d",
[":women_holding_hands_medium_dark_skin_tone_medium_light_skin_tone:"] = "\U0001f46d",
[":women_holding_hands_tone4_tone3:"] = "\U0001f46d",
[":women_holding_hands_medium_dark_skin_tone_medium_skin_tone:"] = "\U0001f46d",
[":women_holding_hands_tone4_tone5:"] = "\U0001f46d",
[":women_holding_hands_medium_dark_skin_tone_dark_skin_tone:"] = "\U0001f46d",
[":women_holding_hands_tone5:"] = "\U0001f46d",
[":women_holding_hands_dark_skin_tone:"] = "\U0001f46d",
[":women_holding_hands_tone5_tone1:"] = "\U0001f46d",
[":women_holding_hands_dark_skin_tone_light_skin_tone:"] = "\U0001f46d",
[":women_holding_hands_tone5_tone2:"] = "\U0001f46d",
[":women_holding_hands_dark_skin_tone_medium_light_skin_tone:"] = "\U0001f46d",
[":women_holding_hands_tone5_tone3:"] = "\U0001f46d",
[":women_holding_hands_dark_skin_tone_medium_skin_tone:"] = "\U0001f46d",
[":women_holding_hands_tone5_tone4:"] = "\U0001f46d",
[":women_holding_hands_dark_skin_tone_medium_dark_skin_tone:"] = "\U0001f46d",
[":women_with_bunny_ears_partying:"] = "\U0001f46f\u200d\u2640\ufe0f",
[":women_wrestling:"] = "\U0001f93c\u200d\u2640\ufe0f",
[":womens:"] = "\U0001f6ba",
[":wood:"] = "\U0001fab5",
[":woozy_face:"] = "\U0001f974",
[":worm:"] = "\U0001fab1",
[":worried:"] = "\U0001f61f",
[":wrench:"] = "\U0001f527",
[":writing_hand:"] = "\u270d\ufe0f",
[":writing_hand_tone1:"] = "\u270d\U0001f3fb",
[":writing_hand::skin-tone-1:"] = "\u270d\U0001f3fb",
[":writing_hand_tone2:"] = "\u270d\U0001f3fc",
[":writing_hand::skin-tone-2:"] = "\u270d\U0001f3fc",
[":writing_hand_tone3:"] = "\u270d\U0001f3fd",
[":writing_hand::skin-tone-3:"] = "\u270d\U0001f3fd",
[":writing_hand_tone4:"] = "\u270d\U0001f3fe",
[":writing_hand::skin-tone-4:"] = "\u270d\U0001f3fe",
[":writing_hand_tone5:"] = "\u270d\U0001f3ff",
[":writing_hand::skin-tone-5:"] = "\u270d\U0001f3ff",
[":x:"] = "\u274c",
[":yarn:"] = "\U0001f9f6",
[":yawning_face:"] = "\U0001f971",
[":yellow_circle:"] = "\U0001f7e1",
[":yellow_heart:"] = "\U0001f49b",
[":yellow_square:"] = "\U0001f7e8",
[":yen:"] = "\U0001f4b4",
[":yin_yang:"] = "\u262f\ufe0f",
[":yo_yo:"] = "\U0001fa80",
[":yum:"] = "\U0001f60b",
[":zany_face:"] = "\U0001f92a",
[":zap:"] = "\u26a1",
[":zebra:"] = "\U0001f993",
[":zero:"] = "\u0030\ufe0f\u20e3",
[":zipper_mouth:"] = "\U0001f910",
[":zipper_mouth_face:"] = "\U0001f910",
[":zombie:"] = "\U0001f9df",
[":zzz:"] = "\U0001f4a4",
};
s_discordNameLookup = new Dictionary<string, string>
{
["\U0001f4af"] = ":100:",
["\U0001f522"] = ":1234:",
["\U0001f3b1"] = ":8ball:",
["\U0001f170\ufe0f"] = ":a:",
["\U0001f170"] = ":a:",
["\U0001f18e"] = ":ab:",
["\U0001f9ee"] = ":abacus:",
["\U0001f524"] = ":abc:",
["\U0001f521"] = ":abcd:",
["\U0001f251"] = ":accept:",
["\U0001fa97"] = ":accordion:",
["\U0001fa79"] = ":adhesive_bandage:",
["\U0001f9d1"] = ":adult:",
["\U0001f9d1\U0001f3fb"] = ":adult_tone1:",
["\U0001f9d1\U0001f3fc"] = ":adult_tone2:",
["\U0001f9d1\U0001f3fd"] = ":adult_tone3:",
["\U0001f9d1\U0001f3fe"] = ":adult_tone4:",
["\U0001f9d1\U0001f3ff"] = ":adult_tone5:",
["\U0001f6a1"] = ":aerial_tramway:",
["\u2708\ufe0f"] = ":airplane:",
["\u2708"] = ":airplane:",
["\U0001f6ec"] = ":airplane_arriving:",
["\U0001f6eb"] = ":airplane_departure:",
["\U0001f6e9\ufe0f"] = ":airplane_small:",
["\U0001f6e9"] = ":airplane_small:",
["\u23f0"] = ":alarm_clock:",
["\u2697\ufe0f"] = ":alembic:",
["\u2697"] = ":alembic:",
["\U0001f47d"] = ":alien:",
["\U0001f691"] = ":ambulance:",
["\U0001f3fa"] = ":amphora:",
["\U0001fac0"] = ":anatomical_heart:",
["\u2693"] = ":anchor:",
["\U0001f47c"] = ":angel:",
["\U0001f47c\U0001f3fb"] = ":angel_tone1:",
["\U0001f47c\U0001f3fc"] = ":angel_tone2:",
["\U0001f47c\U0001f3fd"] = ":angel_tone3:",
["\U0001f47c\U0001f3fe"] = ":angel_tone4:",
["\U0001f47c\U0001f3ff"] = ":angel_tone5:",
["\U0001f4a2"] = ":anger:",
["\U0001f5ef\ufe0f"] = ":anger_right:",
["\U0001f5ef"] = ":anger_right:",
["\U0001f620"] = ":angry:",
["\U0001f627"] = ":anguished:",
["\U0001f41c"] = ":ant:",
["\U0001f34e"] = ":apple:",
["\u2652"] = ":aquarius:",
["\u2648"] = ":aries:",
["\u25c0\ufe0f"] = ":arrow_backward:",
["\u25c0"] = ":arrow_backward:",
["\u23ec"] = ":arrow_double_down:",
["\u23eb"] = ":arrow_double_up:",
["\u2b07\ufe0f"] = ":arrow_down:",
["\u2b07"] = ":arrow_down:",
["\U0001f53d"] = ":arrow_down_small:",
["\u25b6\ufe0f"] = ":arrow_forward:",
["\u25b6"] = ":arrow_forward:",
["\u2935\ufe0f"] = ":arrow_heading_down:",
["\u2935"] = ":arrow_heading_down:",
["\u2934\ufe0f"] = ":arrow_heading_up:",
["\u2934"] = ":arrow_heading_up:",
["\u2b05\ufe0f"] = ":arrow_left:",
["\u2b05"] = ":arrow_left:",
["\u2199\ufe0f"] = ":arrow_lower_left:",
["\u2199"] = ":arrow_lower_left:",
["\u2198\ufe0f"] = ":arrow_lower_right:",
["\u2198"] = ":arrow_lower_right:",
["\u27a1\ufe0f"] = ":arrow_right:",
["\u27a1"] = ":arrow_right:",
["\u21aa\ufe0f"] = ":arrow_right_hook:",
["\u21aa"] = ":arrow_right_hook:",
["\u2b06\ufe0f"] = ":arrow_up:",
["\u2b06"] = ":arrow_up:",
["\u2195\ufe0f"] = ":arrow_up_down:",
["\u2195"] = ":arrow_up_down:",
["\U0001f53c"] = ":arrow_up_small:",
["\u2196\ufe0f"] = ":arrow_upper_left:",
["\u2196"] = ":arrow_upper_left:",
["\u2197\ufe0f"] = ":arrow_upper_right:",
["\u2197"] = ":arrow_upper_right:",
["\U0001f503"] = ":arrows_clockwise:",
["\U0001f504"] = ":arrows_counterclockwise:",
["\U0001f3a8"] = ":art:",
["\U0001f69b"] = ":articulated_lorry:",
["\U0001f9d1\u200d\U0001f3a8"] = ":artist:",
["\U0001f9d1\U0001f3fb\u200d\U0001f3a8"] = ":artist_tone1:",
["\U0001f9d1\U0001f3fc\u200d\U0001f3a8"] = ":artist_tone2:",
["\U0001f9d1\U0001f3fd\u200d\U0001f3a8"] = ":artist_tone3:",
["\U0001f9d1\U0001f3fe\u200d\U0001f3a8"] = ":artist_tone4:",
["\U0001f9d1\U0001f3ff\u200d\U0001f3a8"] = ":artist_tone5:",
["\u002a\ufe0f\u20e3"] = ":asterisk:",
["\u002a\u20e3"] = ":asterisk:",
["\U0001f632"] = ":astonished:",
["\U0001f9d1\u200d\U0001f680"] = ":astronaut:",
["\U0001f9d1\U0001f3fb\u200d\U0001f680"] = ":astronaut_tone1:",
["\U0001f9d1\U0001f3fc\u200d\U0001f680"] = ":astronaut_tone2:",
["\U0001f9d1\U0001f3fd\u200d\U0001f680"] = ":astronaut_tone3:",
["\U0001f9d1\U0001f3fe\u200d\U0001f680"] = ":astronaut_tone4:",
["\U0001f9d1\U0001f3ff\u200d\U0001f680"] = ":astronaut_tone5:",
["\U0001f45f"] = ":athletic_shoe:",
["\U0001f3e7"] = ":atm:",
["\u269b\ufe0f"] = ":atom:",
["\u269b"] = ":atom:",
["\U0001f6fa"] = ":auto_rickshaw:",
["\U0001f951"] = ":avocado:",
["\U0001fa93"] = ":axe:",
["\U0001f171\ufe0f"] = ":b:",
["\U0001f171"] = ":b:",
["\U0001f476"] = ":baby:",
["\U0001f37c"] = ":baby_bottle:",
["\U0001f424"] = ":baby_chick:",
["\U0001f6bc"] = ":baby_symbol:",
["\U0001f476\U0001f3fb"] = ":baby_tone1:",
["\U0001f476\U0001f3fc"] = ":baby_tone2:",
["\U0001f476\U0001f3fd"] = ":baby_tone3:",
["\U0001f476\U0001f3fe"] = ":baby_tone4:",
["\U0001f476\U0001f3ff"] = ":baby_tone5:",
["\U0001f519"] = ":back:",
["\U0001f953"] = ":bacon:",
["\U0001f9a1"] = ":badger:",
["\U0001f3f8"] = ":badminton:",
["\U0001f96f"] = ":bagel:",
["\U0001f6c4"] = ":baggage_claim:",
["\U0001fa70"] = ":ballet_shoes:",
["\U0001f388"] = ":balloon:",
["\U0001f5f3\ufe0f"] = ":ballot_box:",
["\U0001f5f3"] = ":ballot_box:",
["\u2611\ufe0f"] = ":ballot_box_with_check:",
["\u2611"] = ":ballot_box_with_check:",
["\U0001f38d"] = ":bamboo:",
["\U0001f34c"] = ":banana:",
["\u203c\ufe0f"] = ":bangbang:",
["\u203c"] = ":bangbang:",
["\U0001fa95"] = ":banjo:",
["\U0001f3e6"] = ":bank:",
["\U0001f4ca"] = ":bar_chart:",
["\U0001f488"] = ":barber:",
["\u26be"] = ":baseball:",
["\U0001f9fa"] = ":basket:",
["\U0001f3c0"] = ":basketball:",
["\U0001f987"] = ":bat:",
["\U0001f6c0"] = ":bath:",
["\U0001f6c0\U0001f3fb"] = ":bath_tone1:",
["\U0001f6c0\U0001f3fc"] = ":bath_tone2:",
["\U0001f6c0\U0001f3fd"] = ":bath_tone3:",
["\U0001f6c0\U0001f3fe"] = ":bath_tone4:",
["\U0001f6c0\U0001f3ff"] = ":bath_tone5:",
["\U0001f6c1"] = ":bathtub:",
["\U0001f50b"] = ":battery:",
["\U0001f3d6\ufe0f"] = ":beach:",
["\U0001f3d6"] = ":beach:",
["\u26f1\ufe0f"] = ":beach_umbrella:",
["\u26f1"] = ":beach_umbrella:",
["\U0001f43b"] = ":bear:",
["\U0001f9d4"] = ":bearded_person:",
["\U0001f9d4\U0001f3fb"] = ":bearded_person_tone1:",
["\U0001f9d4\U0001f3fc"] = ":bearded_person_tone2:",
["\U0001f9d4\U0001f3fd"] = ":bearded_person_tone3:",
["\U0001f9d4\U0001f3fe"] = ":bearded_person_tone4:",
["\U0001f9d4\U0001f3ff"] = ":bearded_person_tone5:",
["\U0001f9ab"] = ":beaver:",
["\U0001f6cf\ufe0f"] = ":bed:",
["\U0001f6cf"] = ":bed:",
["\U0001f41d"] = ":bee:",
["\U0001f37a"] = ":beer:",
["\U0001f37b"] = ":beers:",
["\U0001fab2"] = ":beetle:",
["\U0001f530"] = ":beginner:",
["\U0001f514"] = ":bell:",
["\U0001fad1"] = ":bell_pepper:",
["\U0001f6ce\ufe0f"] = ":bellhop:",
["\U0001f6ce"] = ":bellhop:",
["\U0001f371"] = ":bento:",
["\U0001f9c3"] = ":beverage_box:",
["\U0001f6b2"] = ":bike:",
["\U0001f459"] = ":bikini:",
["\U0001f9e2"] = ":billed_cap:",
["\u2623\ufe0f"] = ":biohazard:",
["\u2623"] = ":biohazard:",
["\U0001f426"] = ":bird:",
["\U0001f382"] = ":birthday:",
["\U0001f9ac"] = ":bison:",
["\U0001f408\u200d\u2b1b"] = ":black_cat:",
["\u26ab"] = ":black_circle:",
["\U0001f5a4"] = ":black_heart:",
["\U0001f0cf"] = ":black_joker:",
["\u2b1b"] = ":black_large_square:",
["\u25fe"] = ":black_medium_small_square:",
["\u25fc\ufe0f"] = ":black_medium_square:",
["\u25fc"] = ":black_medium_square:",
["\u2712\ufe0f"] = ":black_nib:",
["\u2712"] = ":black_nib:",
["\u25aa\ufe0f"] = ":black_small_square:",
["\u25aa"] = ":black_small_square:",
["\U0001f532"] = ":black_square_button:",
["\U0001f471\u200d\u2642\ufe0f"] = ":blond_haired_man:",
["\U0001f471\U0001f3fb\u200d\u2642\ufe0f"] = ":blond_haired_man_tone1:",
["\U0001f471\U0001f3fc\u200d\u2642\ufe0f"] = ":blond_haired_man_tone2:",
["\U0001f471\U0001f3fd\u200d\u2642\ufe0f"] = ":blond_haired_man_tone3:",
["\U0001f471\U0001f3fe\u200d\u2642\ufe0f"] = ":blond_haired_man_tone4:",
["\U0001f471\U0001f3ff\u200d\u2642\ufe0f"] = ":blond_haired_man_tone5:",
["\U0001f471"] = ":blond_haired_person:",
["\U0001f471\U0001f3fb"] = ":blond_haired_person_tone1:",
["\U0001f471\U0001f3fc"] = ":blond_haired_person_tone2:",
["\U0001f471\U0001f3fd"] = ":blond_haired_person_tone3:",
["\U0001f471\U0001f3fe"] = ":blond_haired_person_tone4:",
["\U0001f471\U0001f3ff"] = ":blond_haired_person_tone5:",
["\U0001f471\u200d\u2640\ufe0f"] = ":blond_haired_woman:",
["\U0001f471\U0001f3fb\u200d\u2640\ufe0f"] = ":blond_haired_woman_tone1:",
["\U0001f471\U0001f3fc\u200d\u2640\ufe0f"] = ":blond_haired_woman_tone2:",
["\U0001f471\U0001f3fd\u200d\u2640\ufe0f"] = ":blond_haired_woman_tone3:",
["\U0001f471\U0001f3fe\u200d\u2640\ufe0f"] = ":blond_haired_woman_tone4:",
["\U0001f471\U0001f3ff\u200d\u2640\ufe0f"] = ":blond_haired_woman_tone5:",
["\U0001f33c"] = ":blossom:",
["\U0001f421"] = ":blowfish:",
["\U0001f4d8"] = ":blue_book:",
["\U0001f699"] = ":blue_car:",
["\U0001f535"] = ":blue_circle:",
["\U0001f499"] = ":blue_heart:",
["\U0001f7e6"] = ":blue_square:",
["\U0001fad0"] = ":blueberries:",
["\U0001f60a"] = ":blush:",
["\U0001f417"] = ":boar:",
["\U0001f4a3"] = ":bomb:",
["\U0001f9b4"] = ":bone:",
["\U0001f4d6"] = ":book:",
["\U0001f516"] = ":bookmark:",
["\U0001f4d1"] = ":bookmark_tabs:",
["\U0001f4da"] = ":books:",
["\U0001f4a5"] = ":boom:",
["\U0001fa83"] = ":boomerang:",
["\U0001f462"] = ":boot:",
["\U0001f490"] = ":bouquet:",
["\U0001f3f9"] = ":bow_and_arrow:",
["\U0001f963"] = ":bowl_with_spoon:",
["\U0001f3b3"] = ":bowling:",
["\U0001f94a"] = ":boxing_glove:",
["\U0001f466"] = ":boy:",
["\U0001f466\U0001f3fb"] = ":boy_tone1:",
["\U0001f466\U0001f3fc"] = ":boy_tone2:",
["\U0001f466\U0001f3fd"] = ":boy_tone3:",
["\U0001f466\U0001f3fe"] = ":boy_tone4:",
["\U0001f466\U0001f3ff"] = ":boy_tone5:",
["\U0001f9e0"] = ":brain:",
["\U0001f35e"] = ":bread:",
["\U0001f931"] = ":breast_feeding:",
["\U0001f931\U0001f3fb"] = ":breast_feeding_tone1:",
["\U0001f931\U0001f3fc"] = ":breast_feeding_tone2:",
["\U0001f931\U0001f3fd"] = ":breast_feeding_tone3:",
["\U0001f931\U0001f3fe"] = ":breast_feeding_tone4:",
["\U0001f931\U0001f3ff"] = ":breast_feeding_tone5:",
["\U0001f9f1"] = ":bricks:",
["\U0001f309"] = ":bridge_at_night:",
["\U0001f4bc"] = ":briefcase:",
["\U0001fa72"] = ":briefs:",
["\U0001f966"] = ":broccoli:",
["\U0001f494"] = ":broken_heart:",
["\U0001f9f9"] = ":broom:",
["\U0001f7e4"] = ":brown_circle:",
["\U0001f90e"] = ":brown_heart:",
["\U0001f7eb"] = ":brown_square:",
["\U0001f9cb"] = ":bubble_tea:",
["\U0001faa3"] = ":bucket:",
["\U0001f41b"] = ":bug:",
["\U0001f4a1"] = ":bulb:",
["\U0001f685"] = ":bullettrain_front:",
["\U0001f684"] = ":bullettrain_side:",
["\U0001f32f"] = ":burrito:",
["\U0001f68c"] = ":bus:",
["\U0001f68f"] = ":busstop:",
["\U0001f464"] = ":bust_in_silhouette:",
["\U0001f465"] = ":busts_in_silhouette:",
["\U0001f9c8"] = ":butter:",
["\U0001f98b"] = ":butterfly:",
["\U0001f335"] = ":cactus:",
["\U0001f370"] = ":cake:",
["\U0001f4c6"] = ":calendar:",
["\U0001f5d3\ufe0f"] = ":calendar_spiral:",
["\U0001f5d3"] = ":calendar_spiral:",
["\U0001f919"] = ":call_me:",
["\U0001f919\U0001f3fb"] = ":call_me_tone1:",
["\U0001f919\U0001f3fc"] = ":call_me_tone2:",
["\U0001f919\U0001f3fd"] = ":call_me_tone3:",
["\U0001f919\U0001f3fe"] = ":call_me_tone4:",
["\U0001f919\U0001f3ff"] = ":call_me_tone5:",
["\U0001f4f2"] = ":calling:",
["\U0001f42b"] = ":camel:",
["\U0001f4f7"] = ":camera:",
["\U0001f4f8"] = ":camera_with_flash:",
["\U0001f3d5\ufe0f"] = ":camping:",
["\U0001f3d5"] = ":camping:",
["\u264b"] = ":cancer:",
["\U0001f56f\ufe0f"] = ":candle:",
["\U0001f56f"] = ":candle:",
["\U0001f36c"] = ":candy:",
["\U0001f96b"] = ":canned_food:",
["\U0001f6f6"] = ":canoe:",
["\U0001f520"] = ":capital_abcd:",
["\u2651"] = ":capricorn:",
["\U0001f5c3\ufe0f"] = ":card_box:",
["\U0001f5c3"] = ":card_box:",
["\U0001f4c7"] = ":card_index:",
["\U0001f3a0"] = ":carousel_horse:",
["\U0001fa9a"] = ":carpentry_saw:",
["\U0001f955"] = ":carrot:",
["\U0001f431"] = ":cat:",
["\U0001f408"] = ":cat2:",
["\U0001f4bf"] = ":cd:",
["\u26d3\ufe0f"] = ":chains:",
["\u26d3"] = ":chains:",
["\U0001fa91"] = ":chair:",
["\U0001f37e"] = ":champagne:",
["\U0001f942"] = ":champagne_glass:",
["\U0001f4b9"] = ":chart:",
["\U0001f4c9"] = ":chart_with_downwards_trend:",
["\U0001f4c8"] = ":chart_with_upwards_trend:",
["\U0001f3c1"] = ":checkered_flag:",
["\U0001f9c0"] = ":cheese:",
["\U0001f352"] = ":cherries:",
["\U0001f338"] = ":cherry_blossom:",
["\u265f\ufe0f"] = ":chess_pawn:",
["\u265f"] = ":chess_pawn:",
["\U0001f330"] = ":chestnut:",
["\U0001f414"] = ":chicken:",
["\U0001f9d2"] = ":child:",
["\U0001f9d2\U0001f3fb"] = ":child_tone1:",
["\U0001f9d2\U0001f3fc"] = ":child_tone2:",
["\U0001f9d2\U0001f3fd"] = ":child_tone3:",
["\U0001f9d2\U0001f3fe"] = ":child_tone4:",
["\U0001f9d2\U0001f3ff"] = ":child_tone5:",
["\U0001f6b8"] = ":children_crossing:",
["\U0001f43f\ufe0f"] = ":chipmunk:",
["\U0001f43f"] = ":chipmunk:",
["\U0001f36b"] = ":chocolate_bar:",
["\U0001f962"] = ":chopsticks:",
["\U0001f384"] = ":christmas_tree:",
["\u26ea"] = ":church:",
["\U0001f3a6"] = ":cinema:",
["\U0001f3aa"] = ":circus_tent:",
["\U0001f306"] = ":city_dusk:",
["\U0001f307"] = ":city_sunset:",
["\U0001f3d9\ufe0f"] = ":cityscape:",
["\U0001f3d9"] = ":cityscape:",
["\U0001f191"] = ":cl:",
["\U0001f44f"] = ":clap:",
["\U0001f44f\U0001f3fb"] = ":clap_tone1:",
["\U0001f44f\U0001f3fc"] = ":clap_tone2:",
["\U0001f44f\U0001f3fd"] = ":clap_tone3:",
["\U0001f44f\U0001f3fe"] = ":clap_tone4:",
["\U0001f44f\U0001f3ff"] = ":clap_tone5:",
["\U0001f3ac"] = ":clapper:",
["\U0001f3db\ufe0f"] = ":classical_building:",
["\U0001f3db"] = ":classical_building:",
["\U0001f4cb"] = ":clipboard:",
["\U0001f570\ufe0f"] = ":clock:",
["\U0001f570"] = ":clock:",
["\U0001f550"] = ":clock1:",
["\U0001f559"] = ":clock10:",
["\U0001f565"] = ":clock1030:",
["\U0001f55a"] = ":clock11:",
["\U0001f566"] = ":clock1130:",
["\U0001f55b"] = ":clock12:",
["\U0001f567"] = ":clock1230:",
["\U0001f55c"] = ":clock130:",
["\U0001f551"] = ":clock2:",
["\U0001f55d"] = ":clock230:",
["\U0001f552"] = ":clock3:",
["\U0001f55e"] = ":clock330:",
["\U0001f553"] = ":clock4:",
["\U0001f55f"] = ":clock430:",
["\U0001f554"] = ":clock5:",
["\U0001f560"] = ":clock530:",
["\U0001f555"] = ":clock6:",
["\U0001f561"] = ":clock630:",
["\U0001f556"] = ":clock7:",
["\U0001f562"] = ":clock730:",
["\U0001f557"] = ":clock8:",
["\U0001f563"] = ":clock830:",
["\U0001f558"] = ":clock9:",
["\U0001f564"] = ":clock930:",
["\U0001f4d5"] = ":closed_book:",
["\U0001f510"] = ":closed_lock_with_key:",
["\U0001f302"] = ":closed_umbrella:",
["\u2601\ufe0f"] = ":cloud:",
["\u2601"] = ":cloud:",
["\U0001f329\ufe0f"] = ":cloud_lightning:",
["\U0001f329"] = ":cloud_lightning:",
["\U0001f327\ufe0f"] = ":cloud_rain:",
["\U0001f327"] = ":cloud_rain:",
["\U0001f328\ufe0f"] = ":cloud_snow:",
["\U0001f328"] = ":cloud_snow:",
["\U0001f32a\ufe0f"] = ":cloud_tornado:",
["\U0001f32a"] = ":cloud_tornado:",
["\U0001f921"] = ":clown:",
["\u2663\ufe0f"] = ":clubs:",
["\u2663"] = ":clubs:",
["\U0001f9e5"] = ":coat:",
["\U0001fab3"] = ":cockroach:",
["\U0001f378"] = ":cocktail:",
["\U0001f965"] = ":coconut:",
["\u2615"] = ":coffee:",
["\u26b0\ufe0f"] = ":coffin:",
["\u26b0"] = ":coffin:",
["\U0001fa99"] = ":coin:",
["\U0001f976"] = ":cold_face:",
["\U0001f630"] = ":cold_sweat:",
["\u2604\ufe0f"] = ":comet:",
["\u2604"] = ":comet:",
["\U0001f9ed"] = ":compass:",
["\U0001f5dc\ufe0f"] = ":compression:",
["\U0001f5dc"] = ":compression:",
["\U0001f4bb"] = ":computer:",
["\U0001f38a"] = ":confetti_ball:",
["\U0001f616"] = ":confounded:",
["\U0001f615"] = ":confused:",
["\u3297\ufe0f"] = ":congratulations:",
["\u3297"] = ":congratulations:",
["\U0001f6a7"] = ":construction:",
["\U0001f3d7\ufe0f"] = ":construction_site:",
["\U0001f3d7"] = ":construction_site:",
["\U0001f477"] = ":construction_worker:",
["\U0001f477\U0001f3fb"] = ":construction_worker_tone1:",
["\U0001f477\U0001f3fc"] = ":construction_worker_tone2:",
["\U0001f477\U0001f3fd"] = ":construction_worker_tone3:",
["\U0001f477\U0001f3fe"] = ":construction_worker_tone4:",
["\U0001f477\U0001f3ff"] = ":construction_worker_tone5:",
["\U0001f39b\ufe0f"] = ":control_knobs:",
["\U0001f39b"] = ":control_knobs:",
["\U0001f3ea"] = ":convenience_store:",
["\U0001f9d1\u200d\U0001f373"] = ":cook:",
["\U0001f9d1\U0001f3fb\u200d\U0001f373"] = ":cook_tone1:",
["\U0001f9d1\U0001f3fc\u200d\U0001f373"] = ":cook_tone2:",
["\U0001f9d1\U0001f3fd\u200d\U0001f373"] = ":cook_tone3:",
["\U0001f9d1\U0001f3fe\u200d\U0001f373"] = ":cook_tone4:",
["\U0001f9d1\U0001f3ff\u200d\U0001f373"] = ":cook_tone5:",
["\U0001f36a"] = ":cookie:",
["\U0001f373"] = ":cooking:",
["\U0001f192"] = ":cool:",
["\u00a9\ufe0f"] = ":copyright:",
["\u00a9"] = ":copyright:",
["\U0001f33d"] = ":corn:",
["\U0001f6cb\ufe0f"] = ":couch:",
["\U0001f6cb"] = ":couch:",
["\U0001f468\u200d\u2764\ufe0f\u200d\U0001f468"] = ":couple_mm:",
["\U0001f491"] = ":couple_with_heart:",
["\U0001f469\u200d\u2764\ufe0f\u200d\U0001f468"] = ":couple_with_heart_woman_man:",
["\U0001f469\u200d\u2764\ufe0f\u200d\U0001f469"] = ":couple_ww:",
["\U0001f48f"] = ":couplekiss:",
["\U0001f42e"] = ":cow:",
["\U0001f404"] = ":cow2:",
["\U0001f920"] = ":cowboy:",
["\U0001f980"] = ":crab:",
["\U0001f58d\ufe0f"] = ":crayon:",
["\U0001f58d"] = ":crayon:",
["\U0001f4b3"] = ":credit_card:",
["\U0001f319"] = ":crescent_moon:",
["\U0001f997"] = ":cricket:",
["\U0001f3cf"] = ":cricket_game:",
["\U0001f40a"] = ":crocodile:",
["\U0001f950"] = ":croissant:",
["\u271d\ufe0f"] = ":cross:",
["\u271d"] = ":cross:",
["\U0001f38c"] = ":crossed_flags:",
["\u2694\ufe0f"] = ":crossed_swords:",
["\u2694"] = ":crossed_swords:",
["\U0001f451"] = ":crown:",
["\U0001f6f3\ufe0f"] = ":cruise_ship:",
["\U0001f6f3"] = ":cruise_ship:",
["\U0001f622"] = ":cry:",
["\U0001f63f"] = ":crying_cat_face:",
["\U0001f52e"] = ":crystal_ball:",
["\U0001f952"] = ":cucumber:",
["\U0001f964"] = ":cup_with_straw:",
["\U0001f9c1"] = ":cupcake:",
["\U0001f498"] = ":cupid:",
["\U0001f94c"] = ":curling_stone:",
["\u27b0"] = ":curly_loop:",
["\U0001f4b1"] = ":currency_exchange:",
["\U0001f35b"] = ":curry:",
["\U0001f36e"] = ":custard:",
["\U0001f6c3"] = ":customs:",
["\U0001f969"] = ":cut_of_meat:",
["\U0001f300"] = ":cyclone:",
["\U0001f5e1\ufe0f"] = ":dagger:",
["\U0001f5e1"] = ":dagger:",
["\U0001f483"] = ":dancer:",
["\U0001f483\U0001f3fb"] = ":dancer_tone1:",
["\U0001f483\U0001f3fc"] = ":dancer_tone2:",
["\U0001f483\U0001f3fd"] = ":dancer_tone3:",
["\U0001f483\U0001f3fe"] = ":dancer_tone4:",
["\U0001f483\U0001f3ff"] = ":dancer_tone5:",
["\U0001f361"] = ":dango:",
["\U0001f576\ufe0f"] = ":dark_sunglasses:",
["\U0001f576"] = ":dark_sunglasses:",
["\U0001f3af"] = ":dart:",
["\U0001f4a8"] = ":dash:",
["\U0001f4c5"] = ":date:",
["\U0001f9cf\u200d\u2642\ufe0f"] = ":deaf_man:",
["\U0001f9cf\U0001f3fb\u200d\u2642\ufe0f"] = ":deaf_man_tone1:",
["\U0001f9cf\U0001f3fc\u200d\u2642\ufe0f"] = ":deaf_man_tone2:",
["\U0001f9cf\U0001f3fd\u200d\u2642\ufe0f"] = ":deaf_man_tone3:",
["\U0001f9cf\U0001f3fe\u200d\u2642\ufe0f"] = ":deaf_man_tone4:",
["\U0001f9cf\U0001f3ff\u200d\u2642\ufe0f"] = ":deaf_man_tone5:",
["\U0001f9cf"] = ":deaf_person:",
["\U0001f9cf\U0001f3fb"] = ":deaf_person_tone1:",
["\U0001f9cf\U0001f3fc"] = ":deaf_person_tone2:",
["\U0001f9cf\U0001f3fd"] = ":deaf_person_tone3:",
["\U0001f9cf\U0001f3fe"] = ":deaf_person_tone4:",
["\U0001f9cf\U0001f3ff"] = ":deaf_person_tone5:",
["\U0001f9cf\u200d\u2640\ufe0f"] = ":deaf_woman:",
["\U0001f9cf\U0001f3fb\u200d\u2640\ufe0f"] = ":deaf_woman_tone1:",
["\U0001f9cf\U0001f3fc\u200d\u2640\ufe0f"] = ":deaf_woman_tone2:",
["\U0001f9cf\U0001f3fd\u200d\u2640\ufe0f"] = ":deaf_woman_tone3:",
["\U0001f9cf\U0001f3fe\u200d\u2640\ufe0f"] = ":deaf_woman_tone4:",
["\U0001f9cf\U0001f3ff\u200d\u2640\ufe0f"] = ":deaf_woman_tone5:",
["\U0001f333"] = ":deciduous_tree:",
["\U0001f98c"] = ":deer:",
["\U0001f3ec"] = ":department_store:",
["\U0001f3dc\ufe0f"] = ":desert:",
["\U0001f3dc"] = ":desert:",
["\U0001f5a5\ufe0f"] = ":desktop:",
["\U0001f5a5"] = ":desktop:",
["\U0001f575\ufe0f"] = ":detective:",
["\U0001f575"] = ":detective:",
["\U0001f575\U0001f3fb"] = ":detective_tone1:",
["\U0001f575\U0001f3fc"] = ":detective_tone2:",
["\U0001f575\U0001f3fd"] = ":detective_tone3:",
["\U0001f575\U0001f3fe"] = ":detective_tone4:",
["\U0001f575\U0001f3ff"] = ":detective_tone5:",
["\U0001f4a0"] = ":diamond_shape_with_a_dot_inside:",
["\u2666\ufe0f"] = ":diamonds:",
["\u2666"] = ":diamonds:",
["\U0001f61e"] = ":disappointed:",
["\U0001f625"] = ":disappointed_relieved:",
["\U0001f978"] = ":disguised_face:",
["\U0001f5c2\ufe0f"] = ":dividers:",
["\U0001f5c2"] = ":dividers:",
["\U0001f93f"] = ":diving_mask:",
["\U0001fa94"] = ":diya_lamp:",
["\U0001f4ab"] = ":dizzy:",
["\U0001f635"] = ":dizzy_face:",
["\U0001f9ec"] = ":dna:",
["\U0001f6af"] = ":do_not_litter:",
["\U0001f9a4"] = ":dodo:",
["\U0001f436"] = ":dog:",
["\U0001f415"] = ":dog2:",
["\U0001f4b5"] = ":dollar:",
["\U0001f38e"] = ":dolls:",
["\U0001f42c"] = ":dolphin:",
["\U0001f6aa"] = ":door:",
["\U0001f369"] = ":doughnut:",
["\U0001f54a\ufe0f"] = ":dove:",
["\U0001f54a"] = ":dove:",
["\U0001f409"] = ":dragon:",
["\U0001f432"] = ":dragon_face:",
["\U0001f457"] = ":dress:",
["\U0001f42a"] = ":dromedary_camel:",
["\U0001f924"] = ":drooling_face:",
["\U0001fa78"] = ":drop_of_blood:",
["\U0001f4a7"] = ":droplet:",
["\U0001f941"] = ":drum:",
["\U0001f986"] = ":duck:",
["\U0001f95f"] = ":dumpling:",
["\U0001f4c0"] = ":dvd:",
["\U0001f4e7"] = ":e_mail:",
["\U0001f985"] = ":eagle:",
["\U0001f442"] = ":ear:",
["\U0001f33e"] = ":ear_of_rice:",
["\U0001f442\U0001f3fb"] = ":ear_tone1:",
["\U0001f442\U0001f3fc"] = ":ear_tone2:",
["\U0001f442\U0001f3fd"] = ":ear_tone3:",
["\U0001f442\U0001f3fe"] = ":ear_tone4:",
["\U0001f442\U0001f3ff"] = ":ear_tone5:",
["\U0001f9bb"] = ":ear_with_hearing_aid:",
["\U0001f9bb\U0001f3fb"] = ":ear_with_hearing_aid_tone1:",
["\U0001f9bb\U0001f3fc"] = ":ear_with_hearing_aid_tone2:",
["\U0001f9bb\U0001f3fd"] = ":ear_with_hearing_aid_tone3:",
["\U0001f9bb\U0001f3fe"] = ":ear_with_hearing_aid_tone4:",
["\U0001f9bb\U0001f3ff"] = ":ear_with_hearing_aid_tone5:",
["\U0001f30d"] = ":earth_africa:",
["\U0001f30e"] = ":earth_americas:",
["\U0001f30f"] = ":earth_asia:",
["\U0001f95a"] = ":egg:",
["\U0001f346"] = ":eggplant:",
["\u0038\ufe0f\u20e3"] = ":eight:",
["\u0038\u20e3"] = ":eight:",
["\u2734\ufe0f"] = ":eight_pointed_black_star:",
["\u2734"] = ":eight_pointed_black_star:",
["\u2733\ufe0f"] = ":eight_spoked_asterisk:",
["\u2733"] = ":eight_spoked_asterisk:",
["\u23cf\ufe0f"] = ":eject:",
["\u23cf"] = ":eject:",
["\U0001f50c"] = ":electric_plug:",
["\U0001f418"] = ":elephant:",
["\U0001f6d7"] = ":elevator:",
["\U0001f9dd"] = ":elf:",
["\U0001f9dd\U0001f3fb"] = ":elf_tone1:",
["\U0001f9dd\U0001f3fc"] = ":elf_tone2:",
["\U0001f9dd\U0001f3fd"] = ":elf_tone3:",
["\U0001f9dd\U0001f3fe"] = ":elf_tone4:",
["\U0001f9dd\U0001f3ff"] = ":elf_tone5:",
["\U0001f51a"] = ":end:",
["\U0001f3f4\U000e0067\U000e0062\U000e0065\U000e006e\U000e0067\U000e007f"] = ":england:",
["\u2709\ufe0f"] = ":envelope:",
["\u2709"] = ":envelope:",
["\U0001f4e9"] = ":envelope_with_arrow:",
["\U0001f4b6"] = ":euro:",
["\U0001f3f0"] = ":european_castle:",
["\U0001f3e4"] = ":european_post_office:",
["\U0001f332"] = ":evergreen_tree:",
["\u2757"] = ":exclamation:",
["\U0001f92f"] = ":exploding_head:",
["\U0001f611"] = ":expressionless:",
["\U0001f441\ufe0f"] = ":eye:",
["\U0001f441"] = ":eye:",
["\U0001f441\u200d\U0001f5e8"] = ":eye_in_speech_bubble:",
["\U0001f453"] = ":eyeglasses:",
["\U0001f440"] = ":eyes:",
["\U0001f92e"] = ":face_vomiting:",
["\U0001f92d"] = ":face_with_hand_over_mouth:",
["\U0001f9d0"] = ":face_with_monocle:",
["\U0001f928"] = ":face_with_raised_eyebrow:",
["\U0001f92c"] = ":face_with_symbols_over_mouth:",
["\U0001f3ed"] = ":factory:",
["\U0001f9d1\u200d\U0001f3ed"] = ":factory_worker:",
["\U0001f9d1\U0001f3fb\u200d\U0001f3ed"] = ":factory_worker_tone1:",
["\U0001f9d1\U0001f3fc\u200d\U0001f3ed"] = ":factory_worker_tone2:",
["\U0001f9d1\U0001f3fd\u200d\U0001f3ed"] = ":factory_worker_tone3:",
["\U0001f9d1\U0001f3fe\u200d\U0001f3ed"] = ":factory_worker_tone4:",
["\U0001f9d1\U0001f3ff\u200d\U0001f3ed"] = ":factory_worker_tone5:",
["\U0001f9da"] = ":fairy:",
["\U0001f9da\U0001f3fb"] = ":fairy_tone1:",
["\U0001f9da\U0001f3fc"] = ":fairy_tone2:",
["\U0001f9da\U0001f3fd"] = ":fairy_tone3:",
["\U0001f9da\U0001f3fe"] = ":fairy_tone4:",
["\U0001f9da\U0001f3ff"] = ":fairy_tone5:",
["\U0001f9c6"] = ":falafel:",
["\U0001f342"] = ":fallen_leaf:",
["\U0001f46a"] = ":family:",
["\U0001f468\u200d\U0001f466"] = ":family_man_boy:",
["\U0001f468\u200d\U0001f466\u200d\U0001f466"] = ":family_man_boy_boy:",
["\U0001f468\u200d\U0001f467"] = ":family_man_girl:",
["\U0001f468\u200d\U0001f467\u200d\U0001f466"] = ":family_man_girl_boy:",
["\U0001f468\u200d\U0001f467\u200d\U0001f467"] = ":family_man_girl_girl:",
["\U0001f468\u200d\U0001f469\u200d\U0001f466"] = ":family_man_woman_boy:",
["\U0001f468\u200d\U0001f468\u200d\U0001f466"] = ":family_mmb:",
["\U0001f468\u200d\U0001f468\u200d\U0001f466\u200d\U0001f466"] = ":family_mmbb:",
["\U0001f468\u200d\U0001f468\u200d\U0001f467"] = ":family_mmg:",
["\U0001f468\u200d\U0001f468\u200d\U0001f467\u200d\U0001f466"] = ":family_mmgb:",
["\U0001f468\u200d\U0001f468\u200d\U0001f467\u200d\U0001f467"] = ":family_mmgg:",
["\U0001f468\u200d\U0001f469\u200d\U0001f466\u200d\U0001f466"] = ":family_mwbb:",
["\U0001f468\u200d\U0001f469\u200d\U0001f467"] = ":family_mwg:",
["\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f466"] = ":family_mwgb:",
["\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f467"] = ":family_mwgg:",
["\U0001f469\u200d\U0001f466"] = ":family_woman_boy:",
["\U0001f469\u200d\U0001f466\u200d\U0001f466"] = ":family_woman_boy_boy:",
["\U0001f469\u200d\U0001f467"] = ":family_woman_girl:",
["\U0001f469\u200d\U0001f467\u200d\U0001f466"] = ":family_woman_girl_boy:",
["\U0001f469\u200d\U0001f467\u200d\U0001f467"] = ":family_woman_girl_girl:",
["\U0001f469\u200d\U0001f469\u200d\U0001f466"] = ":family_wwb:",
["\U0001f469\u200d\U0001f469\u200d\U0001f466\u200d\U0001f466"] = ":family_wwbb:",
["\U0001f469\u200d\U0001f469\u200d\U0001f467"] = ":family_wwg:",
["\U0001f469\u200d\U0001f469\u200d\U0001f467\u200d\U0001f466"] = ":family_wwgb:",
["\U0001f469\u200d\U0001f469\u200d\U0001f467\u200d\U0001f467"] = ":family_wwgg:",
["\U0001f9d1\u200d\U0001f33e"] = ":farmer:",
["\U0001f9d1\U0001f3fb\u200d\U0001f33e"] = ":farmer_tone1:",
["\U0001f9d1\U0001f3fc\u200d\U0001f33e"] = ":farmer_tone2:",
["\U0001f9d1\U0001f3fd\u200d\U0001f33e"] = ":farmer_tone3:",
["\U0001f9d1\U0001f3fe\u200d\U0001f33e"] = ":farmer_tone4:",
["\U0001f9d1\U0001f3ff\u200d\U0001f33e"] = ":farmer_tone5:",
["\u23e9"] = ":fast_forward:",
["\U0001f4e0"] = ":fax:",
["\U0001f628"] = ":fearful:",
["\U0001fab6"] = ":feather:",
["\U0001f43e"] = ":feet:",
["\u2640\ufe0f"] = ":female_sign:",
["\u2640"] = ":female_sign:",
["\U0001f3a1"] = ":ferris_wheel:",
["\u26f4\ufe0f"] = ":ferry:",
["\u26f4"] = ":ferry:",
["\U0001f3d1"] = ":field_hockey:",
["\U0001f5c4\ufe0f"] = ":file_cabinet:",
["\U0001f5c4"] = ":file_cabinet:",
["\U0001f4c1"] = ":file_folder:",
["\U0001f39e\ufe0f"] = ":film_frames:",
["\U0001f39e"] = ":film_frames:",
["\U0001f91e"] = ":fingers_crossed:",
["\U0001f91e\U0001f3fb"] = ":fingers_crossed_tone1:",
["\U0001f91e\U0001f3fc"] = ":fingers_crossed_tone2:",
["\U0001f91e\U0001f3fd"] = ":fingers_crossed_tone3:",
["\U0001f91e\U0001f3fe"] = ":fingers_crossed_tone4:",
["\U0001f91e\U0001f3ff"] = ":fingers_crossed_tone5:",
["\U0001f525"] = ":fire:",
["\U0001f692"] = ":fire_engine:",
["\U0001f9ef"] = ":fire_extinguisher:",
["\U0001f9e8"] = ":firecracker:",
["\U0001f9d1\u200d\U0001f692"] = ":firefighter:",
["\U0001f9d1\U0001f3fb\u200d\U0001f692"] = ":firefighter_tone1:",
["\U0001f9d1\U0001f3fc\u200d\U0001f692"] = ":firefighter_tone2:",
["\U0001f9d1\U0001f3fd\u200d\U0001f692"] = ":firefighter_tone3:",
["\U0001f9d1\U0001f3fe\u200d\U0001f692"] = ":firefighter_tone4:",
["\U0001f9d1\U0001f3ff\u200d\U0001f692"] = ":firefighter_tone5:",
["\U0001f386"] = ":fireworks:",
["\U0001f947"] = ":first_place:",
["\U0001f313"] = ":first_quarter_moon:",
["\U0001f31b"] = ":first_quarter_moon_with_face:",
["\U0001f41f"] = ":fish:",
["\U0001f365"] = ":fish_cake:",
["\U0001f3a3"] = ":fishing_pole_and_fish:",
["\u270a"] = ":fist:",
["\u270a\U0001f3fb"] = ":fist_tone1:",
["\u270a\U0001f3fc"] = ":fist_tone2:",
["\u270a\U0001f3fd"] = ":fist_tone3:",
["\u270a\U0001f3fe"] = ":fist_tone4:",
["\u270a\U0001f3ff"] = ":fist_tone5:",
["\u0035\ufe0f\u20e3"] = ":five:",
["\u0035\u20e3"] = ":five:",
["\U0001f1e6\U0001f1e8"] = ":flag_ac:",
["\U0001f1e6\U0001f1e9"] = ":flag_ad:",
["\U0001f1e6\U0001f1ea"] = ":flag_ae:",
["\U0001f1e6\U0001f1eb"] = ":flag_af:",
["\U0001f1e6\U0001f1ec"] = ":flag_ag:",
["\U0001f1e6\U0001f1ee"] = ":flag_ai:",
["\U0001f1e6\U0001f1f1"] = ":flag_al:",
["\U0001f1e6\U0001f1f2"] = ":flag_am:",
["\U0001f1e6\U0001f1f4"] = ":flag_ao:",
["\U0001f1e6\U0001f1f6"] = ":flag_aq:",
["\U0001f1e6\U0001f1f7"] = ":flag_ar:",
["\U0001f1e6\U0001f1f8"] = ":flag_as:",
["\U0001f1e6\U0001f1f9"] = ":flag_at:",
["\U0001f1e6\U0001f1fa"] = ":flag_au:",
["\U0001f1e6\U0001f1fc"] = ":flag_aw:",
["\U0001f1e6\U0001f1fd"] = ":flag_ax:",
["\U0001f1e6\U0001f1ff"] = ":flag_az:",
["\U0001f1e7\U0001f1e6"] = ":flag_ba:",
["\U0001f1e7\U0001f1e7"] = ":flag_bb:",
["\U0001f1e7\U0001f1e9"] = ":flag_bd:",
["\U0001f1e7\U0001f1ea"] = ":flag_be:",
["\U0001f1e7\U0001f1eb"] = ":flag_bf:",
["\U0001f1e7\U0001f1ec"] = ":flag_bg:",
["\U0001f1e7\U0001f1ed"] = ":flag_bh:",
["\U0001f1e7\U0001f1ee"] = ":flag_bi:",
["\U0001f1e7\U0001f1ef"] = ":flag_bj:",
["\U0001f1e7\U0001f1f1"] = ":flag_bl:",
["\U0001f3f4"] = ":flag_black:",
["\U0001f1e7\U0001f1f2"] = ":flag_bm:",
["\U0001f1e7\U0001f1f3"] = ":flag_bn:",
["\U0001f1e7\U0001f1f4"] = ":flag_bo:",
["\U0001f1e7\U0001f1f6"] = ":flag_bq:",
["\U0001f1e7\U0001f1f7"] = ":flag_br:",
["\U0001f1e7\U0001f1f8"] = ":flag_bs:",
["\U0001f1e7\U0001f1f9"] = ":flag_bt:",
["\U0001f1e7\U0001f1fb"] = ":flag_bv:",
["\U0001f1e7\U0001f1fc"] = ":flag_bw:",
["\U0001f1e7\U0001f1fe"] = ":flag_by:",
["\U0001f1e7\U0001f1ff"] = ":flag_bz:",
["\U0001f1e8\U0001f1e6"] = ":flag_ca:",
["\U0001f1e8\U0001f1e8"] = ":flag_cc:",
["\U0001f1e8\U0001f1e9"] = ":flag_cd:",
["\U0001f1e8\U0001f1eb"] = ":flag_cf:",
["\U0001f1e8\U0001f1ec"] = ":flag_cg:",
["\U0001f1e8\U0001f1ed"] = ":flag_ch:",
["\U0001f1e8\U0001f1ee"] = ":flag_ci:",
["\U0001f1e8\U0001f1f0"] = ":flag_ck:",
["\U0001f1e8\U0001f1f1"] = ":flag_cl:",
["\U0001f1e8\U0001f1f2"] = ":flag_cm:",
["\U0001f1e8\U0001f1f3"] = ":flag_cn:",
["\U0001f1e8\U0001f1f4"] = ":flag_co:",
["\U0001f1e8\U0001f1f5"] = ":flag_cp:",
["\U0001f1e8\U0001f1f7"] = ":flag_cr:",
["\U0001f1e8\U0001f1fa"] = ":flag_cu:",
["\U0001f1e8\U0001f1fb"] = ":flag_cv:",
["\U0001f1e8\U0001f1fc"] = ":flag_cw:",
["\U0001f1e8\U0001f1fd"] = ":flag_cx:",
["\U0001f1e8\U0001f1fe"] = ":flag_cy:",
["\U0001f1e8\U0001f1ff"] = ":flag_cz:",
["\U0001f1e9\U0001f1ea"] = ":flag_de:",
["\U0001f1e9\U0001f1ec"] = ":flag_dg:",
["\U0001f1e9\U0001f1ef"] = ":flag_dj:",
["\U0001f1e9\U0001f1f0"] = ":flag_dk:",
["\U0001f1e9\U0001f1f2"] = ":flag_dm:",
["\U0001f1e9\U0001f1f4"] = ":flag_do:",
["\U0001f1e9\U0001f1ff"] = ":flag_dz:",
["\U0001f1ea\U0001f1e6"] = ":flag_ea:",
["\U0001f1ea\U0001f1e8"] = ":flag_ec:",
["\U0001f1ea\U0001f1ea"] = ":flag_ee:",
["\U0001f1ea\U0001f1ec"] = ":flag_eg:",
["\U0001f1ea\U0001f1ed"] = ":flag_eh:",
["\U0001f1ea\U0001f1f7"] = ":flag_er:",
["\U0001f1ea\U0001f1f8"] = ":flag_es:",
["\U0001f1ea\U0001f1f9"] = ":flag_et:",
["\U0001f1ea\U0001f1fa"] = ":flag_eu:",
["\U0001f1eb\U0001f1ee"] = ":flag_fi:",
["\U0001f1eb\U0001f1ef"] = ":flag_fj:",
["\U0001f1eb\U0001f1f0"] = ":flag_fk:",
["\U0001f1eb\U0001f1f2"] = ":flag_fm:",
["\U0001f1eb\U0001f1f4"] = ":flag_fo:",
["\U0001f1eb\U0001f1f7"] = ":flag_fr:",
["\U0001f1ec\U0001f1e6"] = ":flag_ga:",
["\U0001f1ec\U0001f1e7"] = ":flag_gb:",
["\U0001f1ec\U0001f1e9"] = ":flag_gd:",
["\U0001f1ec\U0001f1ea"] = ":flag_ge:",
["\U0001f1ec\U0001f1eb"] = ":flag_gf:",
["\U0001f1ec\U0001f1ec"] = ":flag_gg:",
["\U0001f1ec\U0001f1ed"] = ":flag_gh:",
["\U0001f1ec\U0001f1ee"] = ":flag_gi:",
["\U0001f1ec\U0001f1f1"] = ":flag_gl:",
["\U0001f1ec\U0001f1f2"] = ":flag_gm:",
["\U0001f1ec\U0001f1f3"] = ":flag_gn:",
["\U0001f1ec\U0001f1f5"] = ":flag_gp:",
["\U0001f1ec\U0001f1f6"] = ":flag_gq:",
["\U0001f1ec\U0001f1f7"] = ":flag_gr:",
["\U0001f1ec\U0001f1f8"] = ":flag_gs:",
["\U0001f1ec\U0001f1f9"] = ":flag_gt:",
["\U0001f1ec\U0001f1fa"] = ":flag_gu:",
["\U0001f1ec\U0001f1fc"] = ":flag_gw:",
["\U0001f1ec\U0001f1fe"] = ":flag_gy:",
["\U0001f1ed\U0001f1f0"] = ":flag_hk:",
["\U0001f1ed\U0001f1f2"] = ":flag_hm:",
["\U0001f1ed\U0001f1f3"] = ":flag_hn:",
["\U0001f1ed\U0001f1f7"] = ":flag_hr:",
["\U0001f1ed\U0001f1f9"] = ":flag_ht:",
["\U0001f1ed\U0001f1fa"] = ":flag_hu:",
["\U0001f1ee\U0001f1e8"] = ":flag_ic:",
["\U0001f1ee\U0001f1e9"] = ":flag_id:",
["\U0001f1ee\U0001f1ea"] = ":flag_ie:",
["\U0001f1ee\U0001f1f1"] = ":flag_il:",
["\U0001f1ee\U0001f1f2"] = ":flag_im:",
["\U0001f1ee\U0001f1f3"] = ":flag_in:",
["\U0001f1ee\U0001f1f4"] = ":flag_io:",
["\U0001f1ee\U0001f1f6"] = ":flag_iq:",
["\U0001f1ee\U0001f1f7"] = ":flag_ir:",
["\U0001f1ee\U0001f1f8"] = ":flag_is:",
["\U0001f1ee\U0001f1f9"] = ":flag_it:",
["\U0001f1ef\U0001f1ea"] = ":flag_je:",
["\U0001f1ef\U0001f1f2"] = ":flag_jm:",
["\U0001f1ef\U0001f1f4"] = ":flag_jo:",
["\U0001f1ef\U0001f1f5"] = ":flag_jp:",
["\U0001f1f0\U0001f1ea"] = ":flag_ke:",
["\U0001f1f0\U0001f1ec"] = ":flag_kg:",
["\U0001f1f0\U0001f1ed"] = ":flag_kh:",
["\U0001f1f0\U0001f1ee"] = ":flag_ki:",
["\U0001f1f0\U0001f1f2"] = ":flag_km:",
["\U0001f1f0\U0001f1f3"] = ":flag_kn:",
["\U0001f1f0\U0001f1f5"] = ":flag_kp:",
["\U0001f1f0\U0001f1f7"] = ":flag_kr:",
["\U0001f1f0\U0001f1fc"] = ":flag_kw:",
["\U0001f1f0\U0001f1fe"] = ":flag_ky:",
["\U0001f1f0\U0001f1ff"] = ":flag_kz:",
["\U0001f1f1\U0001f1e6"] = ":flag_la:",
["\U0001f1f1\U0001f1e7"] = ":flag_lb:",
["\U0001f1f1\U0001f1e8"] = ":flag_lc:",
["\U0001f1f1\U0001f1ee"] = ":flag_li:",
["\U0001f1f1\U0001f1f0"] = ":flag_lk:",
["\U0001f1f1\U0001f1f7"] = ":flag_lr:",
["\U0001f1f1\U0001f1f8"] = ":flag_ls:",
["\U0001f1f1\U0001f1f9"] = ":flag_lt:",
["\U0001f1f1\U0001f1fa"] = ":flag_lu:",
["\U0001f1f1\U0001f1fb"] = ":flag_lv:",
["\U0001f1f1\U0001f1fe"] = ":flag_ly:",
["\U0001f1f2\U0001f1e6"] = ":flag_ma:",
["\U0001f1f2\U0001f1e8"] = ":flag_mc:",
["\U0001f1f2\U0001f1e9"] = ":flag_md:",
["\U0001f1f2\U0001f1ea"] = ":flag_me:",
["\U0001f1f2\U0001f1eb"] = ":flag_mf:",
["\U0001f1f2\U0001f1ec"] = ":flag_mg:",
["\U0001f1f2\U0001f1ed"] = ":flag_mh:",
["\U0001f1f2\U0001f1f0"] = ":flag_mk:",
["\U0001f1f2\U0001f1f1"] = ":flag_ml:",
["\U0001f1f2\U0001f1f2"] = ":flag_mm:",
["\U0001f1f2\U0001f1f3"] = ":flag_mn:",
["\U0001f1f2\U0001f1f4"] = ":flag_mo:",
["\U0001f1f2\U0001f1f5"] = ":flag_mp:",
["\U0001f1f2\U0001f1f6"] = ":flag_mq:",
["\U0001f1f2\U0001f1f7"] = ":flag_mr:",
["\U0001f1f2\U0001f1f8"] = ":flag_ms:",
["\U0001f1f2\U0001f1f9"] = ":flag_mt:",
["\U0001f1f2\U0001f1fa"] = ":flag_mu:",
["\U0001f1f2\U0001f1fb"] = ":flag_mv:",
["\U0001f1f2\U0001f1fc"] = ":flag_mw:",
["\U0001f1f2\U0001f1fd"] = ":flag_mx:",
["\U0001f1f2\U0001f1fe"] = ":flag_my:",
["\U0001f1f2\U0001f1ff"] = ":flag_mz:",
["\U0001f1f3\U0001f1e6"] = ":flag_na:",
["\U0001f1f3\U0001f1e8"] = ":flag_nc:",
["\U0001f1f3\U0001f1ea"] = ":flag_ne:",
["\U0001f1f3\U0001f1eb"] = ":flag_nf:",
["\U0001f1f3\U0001f1ec"] = ":flag_ng:",
["\U0001f1f3\U0001f1ee"] = ":flag_ni:",
["\U0001f1f3\U0001f1f1"] = ":flag_nl:",
["\U0001f1f3\U0001f1f4"] = ":flag_no:",
["\U0001f1f3\U0001f1f5"] = ":flag_np:",
["\U0001f1f3\U0001f1f7"] = ":flag_nr:",
["\U0001f1f3\U0001f1fa"] = ":flag_nu:",
["\U0001f1f3\U0001f1ff"] = ":flag_nz:",
["\U0001f1f4\U0001f1f2"] = ":flag_om:",
["\U0001f1f5\U0001f1e6"] = ":flag_pa:",
["\U0001f1f5\U0001f1ea"] = ":flag_pe:",
["\U0001f1f5\U0001f1eb"] = ":flag_pf:",
["\U0001f1f5\U0001f1ec"] = ":flag_pg:",
["\U0001f1f5\U0001f1ed"] = ":flag_ph:",
["\U0001f1f5\U0001f1f0"] = ":flag_pk:",
["\U0001f1f5\U0001f1f1"] = ":flag_pl:",
["\U0001f1f5\U0001f1f2"] = ":flag_pm:",
["\U0001f1f5\U0001f1f3"] = ":flag_pn:",
["\U0001f1f5\U0001f1f7"] = ":flag_pr:",
["\U0001f1f5\U0001f1f8"] = ":flag_ps:",
["\U0001f1f5\U0001f1f9"] = ":flag_pt:",
["\U0001f1f5\U0001f1fc"] = ":flag_pw:",
["\U0001f1f5\U0001f1fe"] = ":flag_py:",
["\U0001f1f6\U0001f1e6"] = ":flag_qa:",
["\U0001f1f7\U0001f1ea"] = ":flag_re:",
["\U0001f1f7\U0001f1f4"] = ":flag_ro:",
["\U0001f1f7\U0001f1f8"] = ":flag_rs:",
["\U0001f1f7\U0001f1fa"] = ":flag_ru:",
["\U0001f1f7\U0001f1fc"] = ":flag_rw:",
["\U0001f1f8\U0001f1e6"] = ":flag_sa:",
["\U0001f1f8\U0001f1e7"] = ":flag_sb:",
["\U0001f1f8\U0001f1e8"] = ":flag_sc:",
["\U0001f1f8\U0001f1e9"] = ":flag_sd:",
["\U0001f1f8\U0001f1ea"] = ":flag_se:",
["\U0001f1f8\U0001f1ec"] = ":flag_sg:",
["\U0001f1f8\U0001f1ed"] = ":flag_sh:",
["\U0001f1f8\U0001f1ee"] = ":flag_si:",
["\U0001f1f8\U0001f1ef"] = ":flag_sj:",
["\U0001f1f8\U0001f1f0"] = ":flag_sk:",
["\U0001f1f8\U0001f1f1"] = ":flag_sl:",
["\U0001f1f8\U0001f1f2"] = ":flag_sm:",
["\U0001f1f8\U0001f1f3"] = ":flag_sn:",
["\U0001f1f8\U0001f1f4"] = ":flag_so:",
["\U0001f1f8\U0001f1f7"] = ":flag_sr:",
["\U0001f1f8\U0001f1f8"] = ":flag_ss:",
["\U0001f1f8\U0001f1f9"] = ":flag_st:",
["\U0001f1f8\U0001f1fb"] = ":flag_sv:",
["\U0001f1f8\U0001f1fd"] = ":flag_sx:",
["\U0001f1f8\U0001f1fe"] = ":flag_sy:",
["\U0001f1f8\U0001f1ff"] = ":flag_sz:",
["\U0001f1f9\U0001f1e6"] = ":flag_ta:",
["\U0001f1f9\U0001f1e8"] = ":flag_tc:",
["\U0001f1f9\U0001f1e9"] = ":flag_td:",
["\U0001f1f9\U0001f1eb"] = ":flag_tf:",
["\U0001f1f9\U0001f1ec"] = ":flag_tg:",
["\U0001f1f9\U0001f1ed"] = ":flag_th:",
["\U0001f1f9\U0001f1ef"] = ":flag_tj:",
["\U0001f1f9\U0001f1f0"] = ":flag_tk:",
["\U0001f1f9\U0001f1f1"] = ":flag_tl:",
["\U0001f1f9\U0001f1f2"] = ":flag_tm:",
["\U0001f1f9\U0001f1f3"] = ":flag_tn:",
["\U0001f1f9\U0001f1f4"] = ":flag_to:",
["\U0001f1f9\U0001f1f7"] = ":flag_tr:",
["\U0001f1f9\U0001f1f9"] = ":flag_tt:",
["\U0001f1f9\U0001f1fb"] = ":flag_tv:",
["\U0001f1f9\U0001f1fc"] = ":flag_tw:",
["\U0001f1f9\U0001f1ff"] = ":flag_tz:",
["\U0001f1fa\U0001f1e6"] = ":flag_ua:",
["\U0001f1fa\U0001f1ec"] = ":flag_ug:",
["\U0001f1fa\U0001f1f2"] = ":flag_um:",
["\U0001f1fa\U0001f1f8"] = ":flag_us:",
["\U0001f1fa\U0001f1fe"] = ":flag_uy:",
["\U0001f1fa\U0001f1ff"] = ":flag_uz:",
["\U0001f1fb\U0001f1e6"] = ":flag_va:",
["\U0001f1fb\U0001f1e8"] = ":flag_vc:",
["\U0001f1fb\U0001f1ea"] = ":flag_ve:",
["\U0001f1fb\U0001f1ec"] = ":flag_vg:",
["\U0001f1fb\U0001f1ee"] = ":flag_vi:",
["\U0001f1fb\U0001f1f3"] = ":flag_vn:",
["\U0001f1fb\U0001f1fa"] = ":flag_vu:",
["\U0001f1fc\U0001f1eb"] = ":flag_wf:",
["\U0001f3f3\ufe0f"] = ":flag_white:",
["\U0001f3f3"] = ":flag_white:",
["\U0001f1fc\U0001f1f8"] = ":flag_ws:",
["\U0001f1fd\U0001f1f0"] = ":flag_xk:",
["\U0001f1fe\U0001f1ea"] = ":flag_ye:",
["\U0001f1fe\U0001f1f9"] = ":flag_yt:",
["\U0001f1ff\U0001f1e6"] = ":flag_za:",
["\U0001f1ff\U0001f1f2"] = ":flag_zm:",
["\U0001f1ff\U0001f1fc"] = ":flag_zw:",
["\U0001f38f"] = ":flags:",
["\U0001f9a9"] = ":flamingo:",
["\U0001f526"] = ":flashlight:",
["\U0001fad3"] = ":flatbread:",
["\u269c\ufe0f"] = ":fleur_de_lis:",
["\u269c"] = ":fleur_de_lis:",
["\U0001f4be"] = ":floppy_disk:",
["\U0001f3b4"] = ":flower_playing_cards:",
["\U0001f633"] = ":flushed:",
["\U0001fab0"] = ":fly:",
["\U0001f94f"] = ":flying_disc:",
["\U0001f6f8"] = ":flying_saucer:",
["\U0001f32b\ufe0f"] = ":fog:",
["\U0001f32b"] = ":fog:",
["\U0001f301"] = ":foggy:",
["\U0001fad5"] = ":fondue:",
["\U0001f9b6"] = ":foot:",
["\U0001f9b6\U0001f3fb"] = ":foot_tone1:",
["\U0001f9b6\U0001f3fc"] = ":foot_tone2:",
["\U0001f9b6\U0001f3fd"] = ":foot_tone3:",
["\U0001f9b6\U0001f3fe"] = ":foot_tone4:",
["\U0001f9b6\U0001f3ff"] = ":foot_tone5:",
["\U0001f3c8"] = ":football:",
["\U0001f463"] = ":footprints:",
["\U0001f374"] = ":fork_and_knife:",
["\U0001f37d\ufe0f"] = ":fork_knife_plate:",
["\U0001f37d"] = ":fork_knife_plate:",
["\U0001f960"] = ":fortune_cookie:",
["\u26f2"] = ":fountain:",
["\u0034\ufe0f\u20e3"] = ":four:",
["\u0034\u20e3"] = ":four:",
["\U0001f340"] = ":four_leaf_clover:",
["\U0001f98a"] = ":fox:",
["\U0001f5bc\ufe0f"] = ":frame_photo:",
["\U0001f5bc"] = ":frame_photo:",
["\U0001f193"] = ":free:",
["\U0001f956"] = ":french_bread:",
["\U0001f364"] = ":fried_shrimp:",
["\U0001f35f"] = ":fries:",
["\U0001f438"] = ":frog:",
["\U0001f626"] = ":frowning:",
["\u2639\ufe0f"] = ":frowning2:",
["\u2639"] = ":frowning2:",
["\u26fd"] = ":fuelpump:",
["\U0001f315"] = ":full_moon:",
["\U0001f31d"] = ":full_moon_with_face:",
["\U0001f3b2"] = ":game_die:",
["\U0001f9c4"] = ":garlic:",
["\u2699\ufe0f"] = ":gear:",
["\u2699"] = ":gear:",
["\U0001f48e"] = ":gem:",
["\u264a"] = ":gemini:",
["\U0001f9de"] = ":genie:",
["\U0001f47b"] = ":ghost:",
["\U0001f381"] = ":gift:",
["\U0001f49d"] = ":gift_heart:",
["\U0001f992"] = ":giraffe:",
["\U0001f467"] = ":girl:",
["\U0001f467\U0001f3fb"] = ":girl_tone1:",
["\U0001f467\U0001f3fc"] = ":girl_tone2:",
["\U0001f467\U0001f3fd"] = ":girl_tone3:",
["\U0001f467\U0001f3fe"] = ":girl_tone4:",
["\U0001f467\U0001f3ff"] = ":girl_tone5:",
["\U0001f310"] = ":globe_with_meridians:",
["\U0001f9e4"] = ":gloves:",
["\U0001f945"] = ":goal:",
["\U0001f410"] = ":goat:",
["\U0001f97d"] = ":goggles:",
["\u26f3"] = ":golf:",
["\U0001f98d"] = ":gorilla:",
["\U0001f347"] = ":grapes:",
["\U0001f34f"] = ":green_apple:",
["\U0001f4d7"] = ":green_book:",
["\U0001f7e2"] = ":green_circle:",
["\U0001f49a"] = ":green_heart:",
["\U0001f7e9"] = ":green_square:",
["\u2755"] = ":grey_exclamation:",
["\u2754"] = ":grey_question:",
["\U0001f62c"] = ":grimacing:",
["\U0001f601"] = ":grin:",
["\U0001f600"] = ":grinning:",
["\U0001f482"] = ":guard:",
["\U0001f482\U0001f3fb"] = ":guard_tone1:",
["\U0001f482\U0001f3fc"] = ":guard_tone2:",
["\U0001f482\U0001f3fd"] = ":guard_tone3:",
["\U0001f482\U0001f3fe"] = ":guard_tone4:",
["\U0001f482\U0001f3ff"] = ":guard_tone5:",
["\U0001f9ae"] = ":guide_dog:",
["\U0001f3b8"] = ":guitar:",
["\U0001f52b"] = ":gun:",
["\U0001f354"] = ":hamburger:",
["\U0001f528"] = ":hammer:",
["\u2692\ufe0f"] = ":hammer_pick:",
["\u2692"] = ":hammer_pick:",
["\U0001f439"] = ":hamster:",
["\U0001f590\ufe0f"] = ":hand_splayed:",
["\U0001f590"] = ":hand_splayed:",
["\U0001f590\U0001f3fb"] = ":hand_splayed_tone1:",
["\U0001f590\U0001f3fc"] = ":hand_splayed_tone2:",
["\U0001f590\U0001f3fd"] = ":hand_splayed_tone3:",
["\U0001f590\U0001f3fe"] = ":hand_splayed_tone4:",
["\U0001f590\U0001f3ff"] = ":hand_splayed_tone5:",
["\U0001f45c"] = ":handbag:",
["\U0001f91d"] = ":handshake:",
["\u0023\ufe0f\u20e3"] = ":hash:",
["\u0023\u20e3"] = ":hash:",
["\U0001f425"] = ":hatched_chick:",
["\U0001f423"] = ":hatching_chick:",
["\U0001f915"] = ":head_bandage:",
["\U0001f3a7"] = ":headphones:",
["\U0001faa6"] = ":headstone:",
["\U0001f9d1\u200d\u2695\ufe0f"] = ":health_worker:",
["\U0001f9d1\U0001f3fb\u200d\u2695\ufe0f"] = ":health_worker_tone1:",
["\U0001f9d1\U0001f3fc\u200d\u2695\ufe0f"] = ":health_worker_tone2:",
["\U0001f9d1\U0001f3fd\u200d\u2695\ufe0f"] = ":health_worker_tone3:",
["\U0001f9d1\U0001f3fe\u200d\u2695\ufe0f"] = ":health_worker_tone4:",
["\U0001f9d1\U0001f3ff\u200d\u2695\ufe0f"] = ":health_worker_tone5:",
["\U0001f649"] = ":hear_no_evil:",
["\u2764\ufe0f"] = ":heart:",
["\u2764"] = ":heart:",
["\U0001f49f"] = ":heart_decoration:",
["\u2763\ufe0f"] = ":heart_exclamation:",
["\u2763"] = ":heart_exclamation:",
["\U0001f60d"] = ":heart_eyes:",
["\U0001f63b"] = ":heart_eyes_cat:",
["\U0001f493"] = ":heartbeat:",
["\U0001f497"] = ":heartpulse:",
["\u2665\ufe0f"] = ":hearts:",
["\u2665"] = ":hearts:",
["\u2714\ufe0f"] = ":heavy_check_mark:",
["\u2714"] = ":heavy_check_mark:",
["\u2797"] = ":heavy_division_sign:",
["\U0001f4b2"] = ":heavy_dollar_sign:",
["\u2796"] = ":heavy_minus_sign:",
["\u2716\ufe0f"] = ":heavy_multiplication_x:",
["\u2716"] = ":heavy_multiplication_x:",
["\u2795"] = ":heavy_plus_sign:",
["\U0001f994"] = ":hedgehog:",
["\U0001f681"] = ":helicopter:",
["\u26d1\ufe0f"] = ":helmet_with_cross:",
["\u26d1"] = ":helmet_with_cross:",
["\U0001f33f"] = ":herb:",
["\U0001f33a"] = ":hibiscus:",
["\U0001f506"] = ":high_brightness:",
["\U0001f460"] = ":high_heel:",
["\U0001f97e"] = ":hiking_boot:",
["\U0001f6d5"] = ":hindu_temple:",
["\U0001f99b"] = ":hippopotamus:",
["\U0001f3d2"] = ":hockey:",
["\U0001f573\ufe0f"] = ":hole:",
["\U0001f573"] = ":hole:",
["\U0001f3d8\ufe0f"] = ":homes:",
["\U0001f3d8"] = ":homes:",
["\U0001f36f"] = ":honey_pot:",
["\U0001fa9d"] = ":hook:",
["\U0001f434"] = ":horse:",
["\U0001f3c7"] = ":horse_racing:",
["\U0001f3c7\U0001f3fb"] = ":horse_racing_tone1:",
["\U0001f3c7\U0001f3fc"] = ":horse_racing_tone2:",
["\U0001f3c7\U0001f3fd"] = ":horse_racing_tone3:",
["\U0001f3c7\U0001f3fe"] = ":horse_racing_tone4:",
["\U0001f3c7\U0001f3ff"] = ":horse_racing_tone5:",
["\U0001f3e5"] = ":hospital:",
["\U0001f975"] = ":hot_face:",
["\U0001f336\ufe0f"] = ":hot_pepper:",
["\U0001f336"] = ":hot_pepper:",
["\U0001f32d"] = ":hotdog:",
["\U0001f3e8"] = ":hotel:",
["\u2668\ufe0f"] = ":hotsprings:",
["\u2668"] = ":hotsprings:",
["\u231b"] = ":hourglass:",
["\u23f3"] = ":hourglass_flowing_sand:",
["\U0001f3e0"] = ":house:",
["\U0001f3da\ufe0f"] = ":house_abandoned:",
["\U0001f3da"] = ":house_abandoned:",
["\U0001f3e1"] = ":house_with_garden:",
["\U0001f917"] = ":hugging:",
["\U0001f62f"] = ":hushed:",
["\U0001f6d6"] = ":hut:",
["\U0001f368"] = ":ice_cream:",
["\U0001f9ca"] = ":ice_cube:",
["\u26f8\ufe0f"] = ":ice_skate:",
["\u26f8"] = ":ice_skate:",
["\U0001f366"] = ":icecream:",
["\U0001f194"] = ":id:",
["\U0001f250"] = ":ideograph_advantage:",
["\U0001f47f"] = ":imp:",
["\U0001f4e5"] = ":inbox_tray:",
["\U0001f4e8"] = ":incoming_envelope:",
["\u267e\ufe0f"] = ":infinity:",
["\u267e"] = ":infinity:",
["\u2139\ufe0f"] = ":information_source:",
["\u2139"] = ":information_source:",
["\U0001f607"] = ":innocent:",
["\u2049\ufe0f"] = ":interrobang:",
["\u2049"] = ":interrobang:",
["\U0001f3dd\ufe0f"] = ":island:",
["\U0001f3dd"] = ":island:",
["\U0001f3ee"] = ":izakaya_lantern:",
["\U0001f383"] = ":jack_o_lantern:",
["\U0001f5fe"] = ":japan:",
["\U0001f3ef"] = ":japanese_castle:",
["\U0001f47a"] = ":japanese_goblin:",
["\U0001f479"] = ":japanese_ogre:",
["\U0001f456"] = ":jeans:",
["\U0001f9e9"] = ":jigsaw:",
["\U0001f602"] = ":joy:",
["\U0001f639"] = ":joy_cat:",
["\U0001f579\ufe0f"] = ":joystick:",
["\U0001f579"] = ":joystick:",
["\U0001f9d1\u200d\u2696\ufe0f"] = ":judge:",
["\U0001f9d1\U0001f3fb\u200d\u2696\ufe0f"] = ":judge_tone1:",
["\U0001f9d1\U0001f3fc\u200d\u2696\ufe0f"] = ":judge_tone2:",
["\U0001f9d1\U0001f3fd\u200d\u2696\ufe0f"] = ":judge_tone3:",
["\U0001f9d1\U0001f3fe\u200d\u2696\ufe0f"] = ":judge_tone4:",
["\U0001f9d1\U0001f3ff\u200d\u2696\ufe0f"] = ":judge_tone5:",
["\U0001f54b"] = ":kaaba:",
["\U0001f998"] = ":kangaroo:",
["\U0001f511"] = ":key:",
["\U0001f5dd\ufe0f"] = ":key2:",
["\U0001f5dd"] = ":key2:",
["\u2328\ufe0f"] = ":keyboard:",
["\u2328"] = ":keyboard:",
["\U0001f51f"] = ":keycap_ten:",
["\U0001f458"] = ":kimono:",
["\U0001f48b"] = ":kiss:",
["\U0001f468\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468"] = ":kiss_mm:",
["\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468"] = ":kiss_woman_man:",
["\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469"] = ":kiss_ww:",
["\U0001f617"] = ":kissing:",
["\U0001f63d"] = ":kissing_cat:",
["\U0001f61a"] = ":kissing_closed_eyes:",
["\U0001f618"] = ":kissing_heart:",
["\U0001f619"] = ":kissing_smiling_eyes:",
["\U0001fa81"] = ":kite:",
["\U0001f95d"] = ":kiwi:",
["\U0001f52a"] = ":knife:",
["\U0001faa2"] = ":knot:",
["\U0001f428"] = ":koala:",
["\U0001f201"] = ":koko:",
["\U0001f97c"] = ":lab_coat:",
["\U0001f3f7\ufe0f"] = ":label:",
["\U0001f3f7"] = ":label:",
["\U0001f94d"] = ":lacrosse:",
["\U0001fa9c"] = ":ladder:",
["\U0001f41e"] = ":lady_beetle:",
["\U0001f537"] = ":large_blue_diamond:",
["\U0001f536"] = ":large_orange_diamond:",
["\U0001f317"] = ":last_quarter_moon:",
["\U0001f31c"] = ":last_quarter_moon_with_face:",
["\U0001f606"] = ":laughing:",
["\U0001f96c"] = ":leafy_green:",
["\U0001f343"] = ":leaves:",
["\U0001f4d2"] = ":ledger:",
["\U0001f91b"] = ":left_facing_fist:",
["\U0001f91b\U0001f3fb"] = ":left_facing_fist_tone1:",
["\U0001f91b\U0001f3fc"] = ":left_facing_fist_tone2:",
["\U0001f91b\U0001f3fd"] = ":left_facing_fist_tone3:",
["\U0001f91b\U0001f3fe"] = ":left_facing_fist_tone4:",
["\U0001f91b\U0001f3ff"] = ":left_facing_fist_tone5:",
["\U0001f6c5"] = ":left_luggage:",
["\u2194\ufe0f"] = ":left_right_arrow:",
["\u2194"] = ":left_right_arrow:",
["\u21a9\ufe0f"] = ":leftwards_arrow_with_hook:",
["\u21a9"] = ":leftwards_arrow_with_hook:",
["\U0001f9b5"] = ":leg:",
["\U0001f9b5\U0001f3fb"] = ":leg_tone1:",
["\U0001f9b5\U0001f3fc"] = ":leg_tone2:",
["\U0001f9b5\U0001f3fd"] = ":leg_tone3:",
["\U0001f9b5\U0001f3fe"] = ":leg_tone4:",
["\U0001f9b5\U0001f3ff"] = ":leg_tone5:",
["\U0001f34b"] = ":lemon:",
["\u264c"] = ":leo:",
["\U0001f406"] = ":leopard:",
["\U0001f39a\ufe0f"] = ":level_slider:",
["\U0001f39a"] = ":level_slider:",
["\U0001f574\ufe0f"] = ":levitate:",
["\U0001f574"] = ":levitate:",
["\U0001f574\U0001f3fb"] = ":levitate_tone1:",
["\U0001f574\U0001f3fc"] = ":levitate_tone2:",
["\U0001f574\U0001f3fd"] = ":levitate_tone3:",
["\U0001f574\U0001f3fe"] = ":levitate_tone4:",
["\U0001f574\U0001f3ff"] = ":levitate_tone5:",
["\u264e"] = ":libra:",
["\U0001f688"] = ":light_rail:",
["\U0001f517"] = ":link:",
["\U0001f981"] = ":lion_face:",
["\U0001f444"] = ":lips:",
["\U0001f484"] = ":lipstick:",
["\U0001f98e"] = ":lizard:",
["\U0001f999"] = ":llama:",
["\U0001f99e"] = ":lobster:",
["\U0001f512"] = ":lock:",
["\U0001f50f"] = ":lock_with_ink_pen:",
["\U0001f36d"] = ":lollipop:",
["\U0001fa98"] = ":long_drum:",
["\u27bf"] = ":loop:",
["\U0001f50a"] = ":loud_sound:",
["\U0001f4e2"] = ":loudspeaker:",
["\U0001f3e9"] = ":love_hotel:",
["\U0001f48c"] = ":love_letter:",
["\U0001f91f"] = ":love_you_gesture:",
["\U0001f91f\U0001f3fb"] = ":love_you_gesture_tone1:",
["\U0001f91f\U0001f3fc"] = ":love_you_gesture_tone2:",
["\U0001f91f\U0001f3fd"] = ":love_you_gesture_tone3:",
["\U0001f91f\U0001f3fe"] = ":love_you_gesture_tone4:",
["\U0001f91f\U0001f3ff"] = ":love_you_gesture_tone5:",
["\U0001f505"] = ":low_brightness:",
["\U0001f9f3"] = ":luggage:",
["\U0001fac1"] = ":lungs:",
["\U0001f925"] = ":lying_face:",
["\u24c2\ufe0f"] = ":m:",
["\u24c2"] = ":m:",
["\U0001f50d"] = ":mag:",
["\U0001f50e"] = ":mag_right:",
["\U0001f9d9"] = ":mage:",
["\U0001f9d9\U0001f3fb"] = ":mage_tone1:",
["\U0001f9d9\U0001f3fc"] = ":mage_tone2:",
["\U0001f9d9\U0001f3fd"] = ":mage_tone3:",
["\U0001f9d9\U0001f3fe"] = ":mage_tone4:",
["\U0001f9d9\U0001f3ff"] = ":mage_tone5:",
["\U0001fa84"] = ":magic_wand:",
["\U0001f9f2"] = ":magnet:",
["\U0001f004"] = ":mahjong:",
["\U0001f4eb"] = ":mailbox:",
["\U0001f4ea"] = ":mailbox_closed:",
["\U0001f4ec"] = ":mailbox_with_mail:",
["\U0001f4ed"] = ":mailbox_with_no_mail:",
["\u2642\ufe0f"] = ":male_sign:",
["\u2642"] = ":male_sign:",
["\U0001f9a3"] = ":mammoth:",
["\U0001f468"] = ":man:",
["\U0001f468\u200d\U0001f3a8"] = ":man_artist:",
["\U0001f468\U0001f3fb\u200d\U0001f3a8"] = ":man_artist_tone1:",
["\U0001f468\U0001f3fc\u200d\U0001f3a8"] = ":man_artist_tone2:",
["\U0001f468\U0001f3fd\u200d\U0001f3a8"] = ":man_artist_tone3:",
["\U0001f468\U0001f3fe\u200d\U0001f3a8"] = ":man_artist_tone4:",
["\U0001f468\U0001f3ff\u200d\U0001f3a8"] = ":man_artist_tone5:",
["\U0001f468\u200d\U0001f680"] = ":man_astronaut:",
["\U0001f468\U0001f3fb\u200d\U0001f680"] = ":man_astronaut_tone1:",
["\U0001f468\U0001f3fc\u200d\U0001f680"] = ":man_astronaut_tone2:",
["\U0001f468\U0001f3fd\u200d\U0001f680"] = ":man_astronaut_tone3:",
["\U0001f468\U0001f3fe\u200d\U0001f680"] = ":man_astronaut_tone4:",
["\U0001f468\U0001f3ff\u200d\U0001f680"] = ":man_astronaut_tone5:",
["\U0001f468\u200d\U0001f9b2"] = ":man_bald:",
["\U0001f468\U0001f3fb\u200d\U0001f9b2"] = ":man_bald_tone1:",
["\U0001f468\U0001f3fc\u200d\U0001f9b2"] = ":man_bald_tone2:",
["\U0001f468\U0001f3fd\u200d\U0001f9b2"] = ":man_bald_tone3:",
["\U0001f468\U0001f3fe\u200d\U0001f9b2"] = ":man_bald_tone4:",
["\U0001f468\U0001f3ff\u200d\U0001f9b2"] = ":man_bald_tone5:",
["\U0001f6b4\u200d\u2642\ufe0f"] = ":man_biking:",
["\U0001f6b4\U0001f3fb\u200d\u2642\ufe0f"] = ":man_biking_tone1:",
["\U0001f6b4\U0001f3fc\u200d\u2642\ufe0f"] = ":man_biking_tone2:",
["\U0001f6b4\U0001f3fd\u200d\u2642\ufe0f"] = ":man_biking_tone3:",
["\U0001f6b4\U0001f3fe\u200d\u2642\ufe0f"] = ":man_biking_tone4:",
["\U0001f6b4\U0001f3ff\u200d\u2642\ufe0f"] = ":man_biking_tone5:",
["\u26f9\ufe0f\u200d\u2642\ufe0f"] = ":man_bouncing_ball:",
["\u26f9\U0001f3fb\u200d\u2642\ufe0f"] = ":man_bouncing_ball_tone1:",
["\u26f9\U0001f3fc\u200d\u2642\ufe0f"] = ":man_bouncing_ball_tone2:",
["\u26f9\U0001f3fd\u200d\u2642\ufe0f"] = ":man_bouncing_ball_tone3:",
["\u26f9\U0001f3fe\u200d\u2642\ufe0f"] = ":man_bouncing_ball_tone4:",
["\u26f9\U0001f3ff\u200d\u2642\ufe0f"] = ":man_bouncing_ball_tone5:",
["\U0001f647\u200d\u2642\ufe0f"] = ":man_bowing:",
["\U0001f647\U0001f3fb\u200d\u2642\ufe0f"] = ":man_bowing_tone1:",
["\U0001f647\U0001f3fc\u200d\u2642\ufe0f"] = ":man_bowing_tone2:",
["\U0001f647\U0001f3fd\u200d\u2642\ufe0f"] = ":man_bowing_tone3:",
["\U0001f647\U0001f3fe\u200d\u2642\ufe0f"] = ":man_bowing_tone4:",
["\U0001f647\U0001f3ff\u200d\u2642\ufe0f"] = ":man_bowing_tone5:",
["\U0001f938\u200d\u2642\ufe0f"] = ":man_cartwheeling:",
["\U0001f938\U0001f3fb\u200d\u2642\ufe0f"] = ":man_cartwheeling_tone1:",
["\U0001f938\U0001f3fc\u200d\u2642\ufe0f"] = ":man_cartwheeling_tone2:",
["\U0001f938\U0001f3fd\u200d\u2642\ufe0f"] = ":man_cartwheeling_tone3:",
["\U0001f938\U0001f3fe\u200d\u2642\ufe0f"] = ":man_cartwheeling_tone4:",
["\U0001f938\U0001f3ff\u200d\u2642\ufe0f"] = ":man_cartwheeling_tone5:",
["\U0001f9d7\u200d\u2642\ufe0f"] = ":man_climbing:",
["\U0001f9d7\U0001f3fb\u200d\u2642\ufe0f"] = ":man_climbing_tone1:",
["\U0001f9d7\U0001f3fc\u200d\u2642\ufe0f"] = ":man_climbing_tone2:",
["\U0001f9d7\U0001f3fd\u200d\u2642\ufe0f"] = ":man_climbing_tone3:",
["\U0001f9d7\U0001f3fe\u200d\u2642\ufe0f"] = ":man_climbing_tone4:",
["\U0001f9d7\U0001f3ff\u200d\u2642\ufe0f"] = ":man_climbing_tone5:",
["\U0001f477\u200d\u2642\ufe0f"] = ":man_construction_worker:",
["\U0001f477\U0001f3fb\u200d\u2642\ufe0f"] = ":man_construction_worker_tone1:",
["\U0001f477\U0001f3fc\u200d\u2642\ufe0f"] = ":man_construction_worker_tone2:",
["\U0001f477\U0001f3fd\u200d\u2642\ufe0f"] = ":man_construction_worker_tone3:",
["\U0001f477\U0001f3fe\u200d\u2642\ufe0f"] = ":man_construction_worker_tone4:",
["\U0001f477\U0001f3ff\u200d\u2642\ufe0f"] = ":man_construction_worker_tone5:",
["\U0001f468\u200d\U0001f373"] = ":man_cook:",
["\U0001f468\U0001f3fb\u200d\U0001f373"] = ":man_cook_tone1:",
["\U0001f468\U0001f3fc\u200d\U0001f373"] = ":man_cook_tone2:",
["\U0001f468\U0001f3fd\u200d\U0001f373"] = ":man_cook_tone3:",
["\U0001f468\U0001f3fe\u200d\U0001f373"] = ":man_cook_tone4:",
["\U0001f468\U0001f3ff\u200d\U0001f373"] = ":man_cook_tone5:",
["\U0001f468\u200d\U0001f9b1"] = ":man_curly_haired:",
["\U0001f468\U0001f3fb\u200d\U0001f9b1"] = ":man_curly_haired_tone1:",
["\U0001f468\U0001f3fc\u200d\U0001f9b1"] = ":man_curly_haired_tone2:",
["\U0001f468\U0001f3fd\u200d\U0001f9b1"] = ":man_curly_haired_tone3:",
["\U0001f468\U0001f3fe\u200d\U0001f9b1"] = ":man_curly_haired_tone4:",
["\U0001f468\U0001f3ff\u200d\U0001f9b1"] = ":man_curly_haired_tone5:",
["\U0001f57a"] = ":man_dancing:",
["\U0001f57a\U0001f3fb"] = ":man_dancing_tone1:",
["\U0001f57a\U0001f3fc"] = ":man_dancing_tone2:",
["\U0001f57a\U0001f3fd"] = ":man_dancing_tone3:",
["\U0001f57a\U0001f3fe"] = ":man_dancing_tone4:",
["\U0001f57a\U0001f3ff"] = ":man_dancing_tone5:",
["\U0001f575\ufe0f\u200d\u2642\ufe0f"] = ":man_detective:",
["\U0001f575\U0001f3fb\u200d\u2642\ufe0f"] = ":man_detective_tone1:",
["\U0001f575\U0001f3fc\u200d\u2642\ufe0f"] = ":man_detective_tone2:",
["\U0001f575\U0001f3fd\u200d\u2642\ufe0f"] = ":man_detective_tone3:",
["\U0001f575\U0001f3fe\u200d\u2642\ufe0f"] = ":man_detective_tone4:",
["\U0001f575\U0001f3ff\u200d\u2642\ufe0f"] = ":man_detective_tone5:",
["\U0001f9dd\u200d\u2642\ufe0f"] = ":man_elf:",
["\U0001f9dd\U0001f3fb\u200d\u2642\ufe0f"] = ":man_elf_tone1:",
["\U0001f9dd\U0001f3fc\u200d\u2642\ufe0f"] = ":man_elf_tone2:",
["\U0001f9dd\U0001f3fd\u200d\u2642\ufe0f"] = ":man_elf_tone3:",
["\U0001f9dd\U0001f3fe\u200d\u2642\ufe0f"] = ":man_elf_tone4:",
["\U0001f9dd\U0001f3ff\u200d\u2642\ufe0f"] = ":man_elf_tone5:",
["\U0001f926\u200d\u2642\ufe0f"] = ":man_facepalming:",
["\U0001f926\U0001f3fb\u200d\u2642\ufe0f"] = ":man_facepalming_tone1:",
["\U0001f926\U0001f3fc\u200d\u2642\ufe0f"] = ":man_facepalming_tone2:",
["\U0001f926\U0001f3fd\u200d\u2642\ufe0f"] = ":man_facepalming_tone3:",
["\U0001f926\U0001f3fe\u200d\u2642\ufe0f"] = ":man_facepalming_tone4:",
["\U0001f926\U0001f3ff\u200d\u2642\ufe0f"] = ":man_facepalming_tone5:",
["\U0001f468\u200d\U0001f3ed"] = ":man_factory_worker:",
["\U0001f468\U0001f3fb\u200d\U0001f3ed"] = ":man_factory_worker_tone1:",
["\U0001f468\U0001f3fc\u200d\U0001f3ed"] = ":man_factory_worker_tone2:",
["\U0001f468\U0001f3fd\u200d\U0001f3ed"] = ":man_factory_worker_tone3:",
["\U0001f468\U0001f3fe\u200d\U0001f3ed"] = ":man_factory_worker_tone4:",
["\U0001f468\U0001f3ff\u200d\U0001f3ed"] = ":man_factory_worker_tone5:",
["\U0001f9da\u200d\u2642\ufe0f"] = ":man_fairy:",
["\U0001f9da\U0001f3fb\u200d\u2642\ufe0f"] = ":man_fairy_tone1:",
["\U0001f9da\U0001f3fc\u200d\u2642\ufe0f"] = ":man_fairy_tone2:",
["\U0001f9da\U0001f3fd\u200d\u2642\ufe0f"] = ":man_fairy_tone3:",
["\U0001f9da\U0001f3fe\u200d\u2642\ufe0f"] = ":man_fairy_tone4:",
["\U0001f9da\U0001f3ff\u200d\u2642\ufe0f"] = ":man_fairy_tone5:",
["\U0001f468\u200d\U0001f33e"] = ":man_farmer:",
["\U0001f468\U0001f3fb\u200d\U0001f33e"] = ":man_farmer_tone1:",
["\U0001f468\U0001f3fc\u200d\U0001f33e"] = ":man_farmer_tone2:",
["\U0001f468\U0001f3fd\u200d\U0001f33e"] = ":man_farmer_tone3:",
["\U0001f468\U0001f3fe\u200d\U0001f33e"] = ":man_farmer_tone4:",
["\U0001f468\U0001f3ff\u200d\U0001f33e"] = ":man_farmer_tone5:",
["\U0001f468\u200d\U0001f37c"] = ":man_feeding_baby:",
["\U0001f468\U0001f3fb\u200d\U0001f37c"] = ":man_feeding_baby_tone1:",
["\U0001f468\U0001f3fc\u200d\U0001f37c"] = ":man_feeding_baby_tone2:",
["\U0001f468\U0001f3fd\u200d\U0001f37c"] = ":man_feeding_baby_tone3:",
["\U0001f468\U0001f3fe\u200d\U0001f37c"] = ":man_feeding_baby_tone4:",
["\U0001f468\U0001f3ff\u200d\U0001f37c"] = ":man_feeding_baby_tone5:",
["\U0001f468\u200d\U0001f692"] = ":man_firefighter:",
["\U0001f468\U0001f3fb\u200d\U0001f692"] = ":man_firefighter_tone1:",
["\U0001f468\U0001f3fc\u200d\U0001f692"] = ":man_firefighter_tone2:",
["\U0001f468\U0001f3fd\u200d\U0001f692"] = ":man_firefighter_tone3:",
["\U0001f468\U0001f3fe\u200d\U0001f692"] = ":man_firefighter_tone4:",
["\U0001f468\U0001f3ff\u200d\U0001f692"] = ":man_firefighter_tone5:",
["\U0001f64d\u200d\u2642\ufe0f"] = ":man_frowning:",
["\U0001f64d\U0001f3fb\u200d\u2642\ufe0f"] = ":man_frowning_tone1:",
["\U0001f64d\U0001f3fc\u200d\u2642\ufe0f"] = ":man_frowning_tone2:",
["\U0001f64d\U0001f3fd\u200d\u2642\ufe0f"] = ":man_frowning_tone3:",
["\U0001f64d\U0001f3fe\u200d\u2642\ufe0f"] = ":man_frowning_tone4:",
["\U0001f64d\U0001f3ff\u200d\u2642\ufe0f"] = ":man_frowning_tone5:",
["\U0001f9de\u200d\u2642\ufe0f"] = ":man_genie:",
["\U0001f645\u200d\u2642\ufe0f"] = ":man_gesturing_no:",
["\U0001f645\U0001f3fb\u200d\u2642\ufe0f"] = ":man_gesturing_no_tone1:",
["\U0001f645\U0001f3fc\u200d\u2642\ufe0f"] = ":man_gesturing_no_tone2:",
["\U0001f645\U0001f3fd\u200d\u2642\ufe0f"] = ":man_gesturing_no_tone3:",
["\U0001f645\U0001f3fe\u200d\u2642\ufe0f"] = ":man_gesturing_no_tone4:",
["\U0001f645\U0001f3ff\u200d\u2642\ufe0f"] = ":man_gesturing_no_tone5:",
["\U0001f646\u200d\u2642\ufe0f"] = ":man_gesturing_ok:",
["\U0001f646\U0001f3fb\u200d\u2642\ufe0f"] = ":man_gesturing_ok_tone1:",
["\U0001f646\U0001f3fc\u200d\u2642\ufe0f"] = ":man_gesturing_ok_tone2:",
["\U0001f646\U0001f3fd\u200d\u2642\ufe0f"] = ":man_gesturing_ok_tone3:",
["\U0001f646\U0001f3fe\u200d\u2642\ufe0f"] = ":man_gesturing_ok_tone4:",
["\U0001f646\U0001f3ff\u200d\u2642\ufe0f"] = ":man_gesturing_ok_tone5:",
["\U0001f486\u200d\u2642\ufe0f"] = ":man_getting_face_massage:",
["\U0001f486\U0001f3fb\u200d\u2642\ufe0f"] = ":man_getting_face_massage_tone1:",
["\U0001f486\U0001f3fc\u200d\u2642\ufe0f"] = ":man_getting_face_massage_tone2:",
["\U0001f486\U0001f3fd\u200d\u2642\ufe0f"] = ":man_getting_face_massage_tone3:",
["\U0001f486\U0001f3fe\u200d\u2642\ufe0f"] = ":man_getting_face_massage_tone4:",
["\U0001f486\U0001f3ff\u200d\u2642\ufe0f"] = ":man_getting_face_massage_tone5:",
["\U0001f487\u200d\u2642\ufe0f"] = ":man_getting_haircut:",
["\U0001f487\U0001f3fb\u200d\u2642\ufe0f"] = ":man_getting_haircut_tone1:",
["\U0001f487\U0001f3fc\u200d\u2642\ufe0f"] = ":man_getting_haircut_tone2:",
["\U0001f487\U0001f3fd\u200d\u2642\ufe0f"] = ":man_getting_haircut_tone3:",
["\U0001f487\U0001f3fe\u200d\u2642\ufe0f"] = ":man_getting_haircut_tone4:",
["\U0001f487\U0001f3ff\u200d\u2642\ufe0f"] = ":man_getting_haircut_tone5:",
["\U0001f3cc\ufe0f\u200d\u2642\ufe0f"] = ":man_golfing:",
["\U0001f3cc\U0001f3fb\u200d\u2642\ufe0f"] = ":man_golfing_tone1:",
["\U0001f3cc\U0001f3fc\u200d\u2642\ufe0f"] = ":man_golfing_tone2:",
["\U0001f3cc\U0001f3fd\u200d\u2642\ufe0f"] = ":man_golfing_tone3:",
["\U0001f3cc\U0001f3fe\u200d\u2642\ufe0f"] = ":man_golfing_tone4:",
["\U0001f3cc\U0001f3ff\u200d\u2642\ufe0f"] = ":man_golfing_tone5:",
["\U0001f482\u200d\u2642\ufe0f"] = ":man_guard:",
["\U0001f482\U0001f3fb\u200d\u2642\ufe0f"] = ":man_guard_tone1:",
["\U0001f482\U0001f3fc\u200d\u2642\ufe0f"] = ":man_guard_tone2:",
["\U0001f482\U0001f3fd\u200d\u2642\ufe0f"] = ":man_guard_tone3:",
["\U0001f482\U0001f3fe\u200d\u2642\ufe0f"] = ":man_guard_tone4:",
["\U0001f482\U0001f3ff\u200d\u2642\ufe0f"] = ":man_guard_tone5:",
["\U0001f468\u200d\u2695\ufe0f"] = ":man_health_worker:",
["\U0001f468\U0001f3fb\u200d\u2695\ufe0f"] = ":man_health_worker_tone1:",
["\U0001f468\U0001f3fc\u200d\u2695\ufe0f"] = ":man_health_worker_tone2:",
["\U0001f468\U0001f3fd\u200d\u2695\ufe0f"] = ":man_health_worker_tone3:",
["\U0001f468\U0001f3fe\u200d\u2695\ufe0f"] = ":man_health_worker_tone4:",
["\U0001f468\U0001f3ff\u200d\u2695\ufe0f"] = ":man_health_worker_tone5:",
["\U0001f9d8\u200d\u2642\ufe0f"] = ":man_in_lotus_position:",
["\U0001f9d8\U0001f3fb\u200d\u2642\ufe0f"] = ":man_in_lotus_position_tone1:",
["\U0001f9d8\U0001f3fc\u200d\u2642\ufe0f"] = ":man_in_lotus_position_tone2:",
["\U0001f9d8\U0001f3fd\u200d\u2642\ufe0f"] = ":man_in_lotus_position_tone3:",
["\U0001f9d8\U0001f3fe\u200d\u2642\ufe0f"] = ":man_in_lotus_position_tone4:",
["\U0001f9d8\U0001f3ff\u200d\u2642\ufe0f"] = ":man_in_lotus_position_tone5:",
["\U0001f468\u200d\U0001f9bd"] = ":man_in_manual_wheelchair:",
["\U0001f468\U0001f3fb\u200d\U0001f9bd"] = ":man_in_manual_wheelchair_tone1:",
["\U0001f468\U0001f3fc\u200d\U0001f9bd"] = ":man_in_manual_wheelchair_tone2:",
["\U0001f468\U0001f3fd\u200d\U0001f9bd"] = ":man_in_manual_wheelchair_tone3:",
["\U0001f468\U0001f3fe\u200d\U0001f9bd"] = ":man_in_manual_wheelchair_tone4:",
["\U0001f468\U0001f3ff\u200d\U0001f9bd"] = ":man_in_manual_wheelchair_tone5:",
["\U0001f468\u200d\U0001f9bc"] = ":man_in_motorized_wheelchair:",
["\U0001f468\U0001f3fb\u200d\U0001f9bc"] = ":man_in_motorized_wheelchair_tone1:",
["\U0001f468\U0001f3fc\u200d\U0001f9bc"] = ":man_in_motorized_wheelchair_tone2:",
["\U0001f468\U0001f3fd\u200d\U0001f9bc"] = ":man_in_motorized_wheelchair_tone3:",
["\U0001f468\U0001f3fe\u200d\U0001f9bc"] = ":man_in_motorized_wheelchair_tone4:",
["\U0001f468\U0001f3ff\u200d\U0001f9bc"] = ":man_in_motorized_wheelchair_tone5:",
["\U0001f9d6\u200d\u2642\ufe0f"] = ":man_in_steamy_room:",
["\U0001f9d6\U0001f3fb\u200d\u2642\ufe0f"] = ":man_in_steamy_room_tone1:",
["\U0001f9d6\U0001f3fc\u200d\u2642\ufe0f"] = ":man_in_steamy_room_tone2:",
["\U0001f9d6\U0001f3fd\u200d\u2642\ufe0f"] = ":man_in_steamy_room_tone3:",
["\U0001f9d6\U0001f3fe\u200d\u2642\ufe0f"] = ":man_in_steamy_room_tone4:",
["\U0001f9d6\U0001f3ff\u200d\u2642\ufe0f"] = ":man_in_steamy_room_tone5:",
["\U0001f935\u200d\u2642\ufe0f"] = ":man_in_tuxedo:",
["\U0001f935\U0001f3fb\u200d\u2642\ufe0f"] = ":man_in_tuxedo_tone1:",
["\U0001f935\U0001f3fc\u200d\u2642\ufe0f"] = ":man_in_tuxedo_tone2:",
["\U0001f935\U0001f3fd\u200d\u2642\ufe0f"] = ":man_in_tuxedo_tone3:",
["\U0001f935\U0001f3fe\u200d\u2642\ufe0f"] = ":man_in_tuxedo_tone4:",
["\U0001f935\U0001f3ff\u200d\u2642\ufe0f"] = ":man_in_tuxedo_tone5:",
["\U0001f468\u200d\u2696\ufe0f"] = ":man_judge:",
["\U0001f468\U0001f3fb\u200d\u2696\ufe0f"] = ":man_judge_tone1:",
["\U0001f468\U0001f3fc\u200d\u2696\ufe0f"] = ":man_judge_tone2:",
["\U0001f468\U0001f3fd\u200d\u2696\ufe0f"] = ":man_judge_tone3:",
["\U0001f468\U0001f3fe\u200d\u2696\ufe0f"] = ":man_judge_tone4:",
["\U0001f468\U0001f3ff\u200d\u2696\ufe0f"] = ":man_judge_tone5:",
["\U0001f939\u200d\u2642\ufe0f"] = ":man_juggling:",
["\U0001f939\U0001f3fb\u200d\u2642\ufe0f"] = ":man_juggling_tone1:",
["\U0001f939\U0001f3fc\u200d\u2642\ufe0f"] = ":man_juggling_tone2:",
["\U0001f939\U0001f3fd\u200d\u2642\ufe0f"] = ":man_juggling_tone3:",
["\U0001f939\U0001f3fe\u200d\u2642\ufe0f"] = ":man_juggling_tone4:",
["\U0001f939\U0001f3ff\u200d\u2642\ufe0f"] = ":man_juggling_tone5:",
["\U0001f9ce\u200d\u2642\ufe0f"] = ":man_kneeling:",
["\U0001f9ce\U0001f3fb\u200d\u2642\ufe0f"] = ":man_kneeling_tone1:",
["\U0001f9ce\U0001f3fc\u200d\u2642\ufe0f"] = ":man_kneeling_tone2:",
["\U0001f9ce\U0001f3fd\u200d\u2642\ufe0f"] = ":man_kneeling_tone3:",
["\U0001f9ce\U0001f3fe\u200d\u2642\ufe0f"] = ":man_kneeling_tone4:",
["\U0001f9ce\U0001f3ff\u200d\u2642\ufe0f"] = ":man_kneeling_tone5:",
["\U0001f3cb\ufe0f\u200d\u2642\ufe0f"] = ":man_lifting_weights:",
["\U0001f3cb\U0001f3fb\u200d\u2642\ufe0f"] = ":man_lifting_weights_tone1:",
["\U0001f3cb\U0001f3fc\u200d\u2642\ufe0f"] = ":man_lifting_weights_tone2:",
["\U0001f3cb\U0001f3fd\u200d\u2642\ufe0f"] = ":man_lifting_weights_tone3:",
["\U0001f3cb\U0001f3fe\u200d\u2642\ufe0f"] = ":man_lifting_weights_tone4:",
["\U0001f3cb\U0001f3ff\u200d\u2642\ufe0f"] = ":man_lifting_weights_tone5:",
["\U0001f9d9\u200d\u2642\ufe0f"] = ":man_mage:",
["\U0001f9d9\U0001f3fb\u200d\u2642\ufe0f"] = ":man_mage_tone1:",
["\U0001f9d9\U0001f3fc\u200d\u2642\ufe0f"] = ":man_mage_tone2:",
["\U0001f9d9\U0001f3fd\u200d\u2642\ufe0f"] = ":man_mage_tone3:",
["\U0001f9d9\U0001f3fe\u200d\u2642\ufe0f"] = ":man_mage_tone4:",
["\U0001f9d9\U0001f3ff\u200d\u2642\ufe0f"] = ":man_mage_tone5:",
["\U0001f468\u200d\U0001f527"] = ":man_mechanic:",
["\U0001f468\U0001f3fb\u200d\U0001f527"] = ":man_mechanic_tone1:",
["\U0001f468\U0001f3fc\u200d\U0001f527"] = ":man_mechanic_tone2:",
["\U0001f468\U0001f3fd\u200d\U0001f527"] = ":man_mechanic_tone3:",
["\U0001f468\U0001f3fe\u200d\U0001f527"] = ":man_mechanic_tone4:",
["\U0001f468\U0001f3ff\u200d\U0001f527"] = ":man_mechanic_tone5:",
["\U0001f6b5\u200d\u2642\ufe0f"] = ":man_mountain_biking:",
["\U0001f6b5\U0001f3fb\u200d\u2642\ufe0f"] = ":man_mountain_biking_tone1:",
["\U0001f6b5\U0001f3fc\u200d\u2642\ufe0f"] = ":man_mountain_biking_tone2:",
["\U0001f6b5\U0001f3fd\u200d\u2642\ufe0f"] = ":man_mountain_biking_tone3:",
["\U0001f6b5\U0001f3fe\u200d\u2642\ufe0f"] = ":man_mountain_biking_tone4:",
["\U0001f6b5\U0001f3ff\u200d\u2642\ufe0f"] = ":man_mountain_biking_tone5:",
["\U0001f468\u200d\U0001f4bc"] = ":man_office_worker:",
["\U0001f468\U0001f3fb\u200d\U0001f4bc"] = ":man_office_worker_tone1:",
["\U0001f468\U0001f3fc\u200d\U0001f4bc"] = ":man_office_worker_tone2:",
["\U0001f468\U0001f3fd\u200d\U0001f4bc"] = ":man_office_worker_tone3:",
["\U0001f468\U0001f3fe\u200d\U0001f4bc"] = ":man_office_worker_tone4:",
["\U0001f468\U0001f3ff\u200d\U0001f4bc"] = ":man_office_worker_tone5:",
["\U0001f468\u200d\u2708\ufe0f"] = ":man_pilot:",
["\U0001f468\U0001f3fb\u200d\u2708\ufe0f"] = ":man_pilot_tone1:",
["\U0001f468\U0001f3fc\u200d\u2708\ufe0f"] = ":man_pilot_tone2:",
["\U0001f468\U0001f3fd\u200d\u2708\ufe0f"] = ":man_pilot_tone3:",
["\U0001f468\U0001f3fe\u200d\u2708\ufe0f"] = ":man_pilot_tone4:",
["\U0001f468\U0001f3ff\u200d\u2708\ufe0f"] = ":man_pilot_tone5:",
["\U0001f93e\u200d\u2642\ufe0f"] = ":man_playing_handball:",
["\U0001f93e\U0001f3fb\u200d\u2642\ufe0f"] = ":man_playing_handball_tone1:",
["\U0001f93e\U0001f3fc\u200d\u2642\ufe0f"] = ":man_playing_handball_tone2:",
["\U0001f93e\U0001f3fd\u200d\u2642\ufe0f"] = ":man_playing_handball_tone3:",
["\U0001f93e\U0001f3fe\u200d\u2642\ufe0f"] = ":man_playing_handball_tone4:",
["\U0001f93e\U0001f3ff\u200d\u2642\ufe0f"] = ":man_playing_handball_tone5:",
["\U0001f93d\u200d\u2642\ufe0f"] = ":man_playing_water_polo:",
["\U0001f93d\U0001f3fb\u200d\u2642\ufe0f"] = ":man_playing_water_polo_tone1:",
["\U0001f93d\U0001f3fc\u200d\u2642\ufe0f"] = ":man_playing_water_polo_tone2:",
["\U0001f93d\U0001f3fd\u200d\u2642\ufe0f"] = ":man_playing_water_polo_tone3:",
["\U0001f93d\U0001f3fe\u200d\u2642\ufe0f"] = ":man_playing_water_polo_tone4:",
["\U0001f93d\U0001f3ff\u200d\u2642\ufe0f"] = ":man_playing_water_polo_tone5:",
["\U0001f46e\u200d\u2642\ufe0f"] = ":man_police_officer:",
["\U0001f46e\U0001f3fb\u200d\u2642\ufe0f"] = ":man_police_officer_tone1:",
["\U0001f46e\U0001f3fc\u200d\u2642\ufe0f"] = ":man_police_officer_tone2:",
["\U0001f46e\U0001f3fd\u200d\u2642\ufe0f"] = ":man_police_officer_tone3:",
["\U0001f46e\U0001f3fe\u200d\u2642\ufe0f"] = ":man_police_officer_tone4:",
["\U0001f46e\U0001f3ff\u200d\u2642\ufe0f"] = ":man_police_officer_tone5:",
["\U0001f64e\u200d\u2642\ufe0f"] = ":man_pouting:",
["\U0001f64e\U0001f3fb\u200d\u2642\ufe0f"] = ":man_pouting_tone1:",
["\U0001f64e\U0001f3fc\u200d\u2642\ufe0f"] = ":man_pouting_tone2:",
["\U0001f64e\U0001f3fd\u200d\u2642\ufe0f"] = ":man_pouting_tone3:",
["\U0001f64e\U0001f3fe\u200d\u2642\ufe0f"] = ":man_pouting_tone4:",
["\U0001f64e\U0001f3ff\u200d\u2642\ufe0f"] = ":man_pouting_tone5:",
["\U0001f64b\u200d\u2642\ufe0f"] = ":man_raising_hand:",
["\U0001f64b\U0001f3fb\u200d\u2642\ufe0f"] = ":man_raising_hand_tone1:",
["\U0001f64b\U0001f3fc\u200d\u2642\ufe0f"] = ":man_raising_hand_tone2:",
["\U0001f64b\U0001f3fd\u200d\u2642\ufe0f"] = ":man_raising_hand_tone3:",
["\U0001f64b\U0001f3fe\u200d\u2642\ufe0f"] = ":man_raising_hand_tone4:",
["\U0001f64b\U0001f3ff\u200d\u2642\ufe0f"] = ":man_raising_hand_tone5:",
["\U0001f468\u200d\U0001f9b0"] = ":man_red_haired:",
["\U0001f468\U0001f3fb\u200d\U0001f9b0"] = ":man_red_haired_tone1:",
["\U0001f468\U0001f3fc\u200d\U0001f9b0"] = ":man_red_haired_tone2:",
["\U0001f468\U0001f3fd\u200d\U0001f9b0"] = ":man_red_haired_tone3:",
["\U0001f468\U0001f3fe\u200d\U0001f9b0"] = ":man_red_haired_tone4:",
["\U0001f468\U0001f3ff\u200d\U0001f9b0"] = ":man_red_haired_tone5:",
["\U0001f6a3\u200d\u2642\ufe0f"] = ":man_rowing_boat:",
["\U0001f6a3\U0001f3fb\u200d\u2642\ufe0f"] = ":man_rowing_boat_tone1:",
["\U0001f6a3\U0001f3fc\u200d\u2642\ufe0f"] = ":man_rowing_boat_tone2:",
["\U0001f6a3\U0001f3fd\u200d\u2642\ufe0f"] = ":man_rowing_boat_tone3:",
["\U0001f6a3\U0001f3fe\u200d\u2642\ufe0f"] = ":man_rowing_boat_tone4:",
["\U0001f6a3\U0001f3ff\u200d\u2642\ufe0f"] = ":man_rowing_boat_tone5:",
["\U0001f3c3\u200d\u2642\ufe0f"] = ":man_running:",
["\U0001f3c3\U0001f3fb\u200d\u2642\ufe0f"] = ":man_running_tone1:",
["\U0001f3c3\U0001f3fc\u200d\u2642\ufe0f"] = ":man_running_tone2:",
["\U0001f3c3\U0001f3fd\u200d\u2642\ufe0f"] = ":man_running_tone3:",
["\U0001f3c3\U0001f3fe\u200d\u2642\ufe0f"] = ":man_running_tone4:",
["\U0001f3c3\U0001f3ff\u200d\u2642\ufe0f"] = ":man_running_tone5:",
["\U0001f468\u200d\U0001f52c"] = ":man_scientist:",
["\U0001f468\U0001f3fb\u200d\U0001f52c"] = ":man_scientist_tone1:",
["\U0001f468\U0001f3fc\u200d\U0001f52c"] = ":man_scientist_tone2:",
["\U0001f468\U0001f3fd\u200d\U0001f52c"] = ":man_scientist_tone3:",
["\U0001f468\U0001f3fe\u200d\U0001f52c"] = ":man_scientist_tone4:",
["\U0001f468\U0001f3ff\u200d\U0001f52c"] = ":man_scientist_tone5:",
["\U0001f937\u200d\u2642\ufe0f"] = ":man_shrugging:",
["\U0001f937\U0001f3fb\u200d\u2642\ufe0f"] = ":man_shrugging_tone1:",
["\U0001f937\U0001f3fc\u200d\u2642\ufe0f"] = ":man_shrugging_tone2:",
["\U0001f937\U0001f3fd\u200d\u2642\ufe0f"] = ":man_shrugging_tone3:",
["\U0001f937\U0001f3fe\u200d\u2642\ufe0f"] = ":man_shrugging_tone4:",
["\U0001f937\U0001f3ff\u200d\u2642\ufe0f"] = ":man_shrugging_tone5:",
["\U0001f468\u200d\U0001f3a4"] = ":man_singer:",
["\U0001f468\U0001f3fb\u200d\U0001f3a4"] = ":man_singer_tone1:",
["\U0001f468\U0001f3fc\u200d\U0001f3a4"] = ":man_singer_tone2:",
["\U0001f468\U0001f3fd\u200d\U0001f3a4"] = ":man_singer_tone3:",
["\U0001f468\U0001f3fe\u200d\U0001f3a4"] = ":man_singer_tone4:",
["\U0001f468\U0001f3ff\u200d\U0001f3a4"] = ":man_singer_tone5:",
["\U0001f9cd\u200d\u2642\ufe0f"] = ":man_standing:",
["\U0001f9cd\U0001f3fb\u200d\u2642\ufe0f"] = ":man_standing_tone1:",
["\U0001f9cd\U0001f3fc\u200d\u2642\ufe0f"] = ":man_standing_tone2:",
["\U0001f9cd\U0001f3fd\u200d\u2642\ufe0f"] = ":man_standing_tone3:",
["\U0001f9cd\U0001f3fe\u200d\u2642\ufe0f"] = ":man_standing_tone4:",
["\U0001f9cd\U0001f3ff\u200d\u2642\ufe0f"] = ":man_standing_tone5:",
["\U0001f468\u200d\U0001f393"] = ":man_student:",
["\U0001f468\U0001f3fb\u200d\U0001f393"] = ":man_student_tone1:",
["\U0001f468\U0001f3fc\u200d\U0001f393"] = ":man_student_tone2:",
["\U0001f468\U0001f3fd\u200d\U0001f393"] = ":man_student_tone3:",
["\U0001f468\U0001f3fe\u200d\U0001f393"] = ":man_student_tone4:",
["\U0001f468\U0001f3ff\u200d\U0001f393"] = ":man_student_tone5:",
["\U0001f9b8\u200d\u2642\ufe0f"] = ":man_superhero:",
["\U0001f9b8\U0001f3fb\u200d\u2642\ufe0f"] = ":man_superhero_tone1:",
["\U0001f9b8\U0001f3fc\u200d\u2642\ufe0f"] = ":man_superhero_tone2:",
["\U0001f9b8\U0001f3fd\u200d\u2642\ufe0f"] = ":man_superhero_tone3:",
["\U0001f9b8\U0001f3fe\u200d\u2642\ufe0f"] = ":man_superhero_tone4:",
["\U0001f9b8\U0001f3ff\u200d\u2642\ufe0f"] = ":man_superhero_tone5:",
["\U0001f9b9\u200d\u2642\ufe0f"] = ":man_supervillain:",
["\U0001f9b9\U0001f3fb\u200d\u2642\ufe0f"] = ":man_supervillain_tone1:",
["\U0001f9b9\U0001f3fc\u200d\u2642\ufe0f"] = ":man_supervillain_tone2:",
["\U0001f9b9\U0001f3fd\u200d\u2642\ufe0f"] = ":man_supervillain_tone3:",
["\U0001f9b9\U0001f3fe\u200d\u2642\ufe0f"] = ":man_supervillain_tone4:",
["\U0001f9b9\U0001f3ff\u200d\u2642\ufe0f"] = ":man_supervillain_tone5:",
["\U0001f3c4\u200d\u2642\ufe0f"] = ":man_surfing:",
["\U0001f3c4\U0001f3fb\u200d\u2642\ufe0f"] = ":man_surfing_tone1:",
["\U0001f3c4\U0001f3fc\u200d\u2642\ufe0f"] = ":man_surfing_tone2:",
["\U0001f3c4\U0001f3fd\u200d\u2642\ufe0f"] = ":man_surfing_tone3:",
["\U0001f3c4\U0001f3fe\u200d\u2642\ufe0f"] = ":man_surfing_tone4:",
["\U0001f3c4\U0001f3ff\u200d\u2642\ufe0f"] = ":man_surfing_tone5:",
["\U0001f3ca\u200d\u2642\ufe0f"] = ":man_swimming:",
["\U0001f3ca\U0001f3fb\u200d\u2642\ufe0f"] = ":man_swimming_tone1:",
["\U0001f3ca\U0001f3fc\u200d\u2642\ufe0f"] = ":man_swimming_tone2:",
["\U0001f3ca\U0001f3fd\u200d\u2642\ufe0f"] = ":man_swimming_tone3:",
["\U0001f3ca\U0001f3fe\u200d\u2642\ufe0f"] = ":man_swimming_tone4:",
["\U0001f3ca\U0001f3ff\u200d\u2642\ufe0f"] = ":man_swimming_tone5:",
["\U0001f468\u200d\U0001f3eb"] = ":man_teacher:",
["\U0001f468\U0001f3fb\u200d\U0001f3eb"] = ":man_teacher_tone1:",
["\U0001f468\U0001f3fc\u200d\U0001f3eb"] = ":man_teacher_tone2:",
["\U0001f468\U0001f3fd\u200d\U0001f3eb"] = ":man_teacher_tone3:",
["\U0001f468\U0001f3fe\u200d\U0001f3eb"] = ":man_teacher_tone4:",
["\U0001f468\U0001f3ff\u200d\U0001f3eb"] = ":man_teacher_tone5:",
["\U0001f468\u200d\U0001f4bb"] = ":man_technologist:",
["\U0001f468\U0001f3fb\u200d\U0001f4bb"] = ":man_technologist_tone1:",
["\U0001f468\U0001f3fc\u200d\U0001f4bb"] = ":man_technologist_tone2:",
["\U0001f468\U0001f3fd\u200d\U0001f4bb"] = ":man_technologist_tone3:",
["\U0001f468\U0001f3fe\u200d\U0001f4bb"] = ":man_technologist_tone4:",
["\U0001f468\U0001f3ff\u200d\U0001f4bb"] = ":man_technologist_tone5:",
["\U0001f481\u200d\u2642\ufe0f"] = ":man_tipping_hand:",
["\U0001f481\U0001f3fb\u200d\u2642\ufe0f"] = ":man_tipping_hand_tone1:",
["\U0001f481\U0001f3fc\u200d\u2642\ufe0f"] = ":man_tipping_hand_tone2:",
["\U0001f481\U0001f3fd\u200d\u2642\ufe0f"] = ":man_tipping_hand_tone3:",
["\U0001f481\U0001f3fe\u200d\u2642\ufe0f"] = ":man_tipping_hand_tone4:",
["\U0001f481\U0001f3ff\u200d\u2642\ufe0f"] = ":man_tipping_hand_tone5:",
["\U0001f468\U0001f3fb"] = ":man_tone1:",
["\U0001f468\U0001f3fc"] = ":man_tone2:",
["\U0001f468\U0001f3fd"] = ":man_tone3:",
["\U0001f468\U0001f3fe"] = ":man_tone4:",
["\U0001f468\U0001f3ff"] = ":man_tone5:",
["\U0001f9db\u200d\u2642\ufe0f"] = ":man_vampire:",
["\U0001f9db\U0001f3fb\u200d\u2642\ufe0f"] = ":man_vampire_tone1:",
["\U0001f9db\U0001f3fc\u200d\u2642\ufe0f"] = ":man_vampire_tone2:",
["\U0001f9db\U0001f3fd\u200d\u2642\ufe0f"] = ":man_vampire_tone3:",
["\U0001f9db\U0001f3fe\u200d\u2642\ufe0f"] = ":man_vampire_tone4:",
["\U0001f9db\U0001f3ff\u200d\u2642\ufe0f"] = ":man_vampire_tone5:",
["\U0001f6b6\u200d\u2642\ufe0f"] = ":man_walking:",
["\U0001f6b6\U0001f3fb\u200d\u2642\ufe0f"] = ":man_walking_tone1:",
["\U0001f6b6\U0001f3fc\u200d\u2642\ufe0f"] = ":man_walking_tone2:",
["\U0001f6b6\U0001f3fd\u200d\u2642\ufe0f"] = ":man_walking_tone3:",
["\U0001f6b6\U0001f3fe\u200d\u2642\ufe0f"] = ":man_walking_tone4:",
["\U0001f6b6\U0001f3ff\u200d\u2642\ufe0f"] = ":man_walking_tone5:",
["\U0001f473\u200d\u2642\ufe0f"] = ":man_wearing_turban:",
["\U0001f473\U0001f3fb\u200d\u2642\ufe0f"] = ":man_wearing_turban_tone1:",
["\U0001f473\U0001f3fc\u200d\u2642\ufe0f"] = ":man_wearing_turban_tone2:",
["\U0001f473\U0001f3fd\u200d\u2642\ufe0f"] = ":man_wearing_turban_tone3:",
["\U0001f473\U0001f3fe\u200d\u2642\ufe0f"] = ":man_wearing_turban_tone4:",
["\U0001f473\U0001f3ff\u200d\u2642\ufe0f"] = ":man_wearing_turban_tone5:",
["\U0001f468\u200d\U0001f9b3"] = ":man_white_haired:",
["\U0001f468\U0001f3fb\u200d\U0001f9b3"] = ":man_white_haired_tone1:",
["\U0001f468\U0001f3fc\u200d\U0001f9b3"] = ":man_white_haired_tone2:",
["\U0001f468\U0001f3fd\u200d\U0001f9b3"] = ":man_white_haired_tone3:",
["\U0001f468\U0001f3fe\u200d\U0001f9b3"] = ":man_white_haired_tone4:",
["\U0001f468\U0001f3ff\u200d\U0001f9b3"] = ":man_white_haired_tone5:",
["\U0001f472"] = ":man_with_chinese_cap:",
["\U0001f472\U0001f3fb"] = ":man_with_chinese_cap_tone1:",
["\U0001f472\U0001f3fc"] = ":man_with_chinese_cap_tone2:",
["\U0001f472\U0001f3fd"] = ":man_with_chinese_cap_tone3:",
["\U0001f472\U0001f3fe"] = ":man_with_chinese_cap_tone4:",
["\U0001f472\U0001f3ff"] = ":man_with_chinese_cap_tone5:",
["\U0001f468\u200d\U0001f9af"] = ":man_with_probing_cane:",
["\U0001f468\U0001f3fb\u200d\U0001f9af"] = ":man_with_probing_cane_tone1:",
["\U0001f468\U0001f3fc\u200d\U0001f9af"] = ":man_with_probing_cane_tone2:",
["\U0001f468\U0001f3fd\u200d\U0001f9af"] = ":man_with_probing_cane_tone3:",
["\U0001f468\U0001f3fe\u200d\U0001f9af"] = ":man_with_probing_cane_tone4:",
["\U0001f468\U0001f3ff\u200d\U0001f9af"] = ":man_with_probing_cane_tone5:",
["\U0001f470\u200d\u2642\ufe0f"] = ":man_with_veil:",
["\U0001f470\U0001f3fb\u200d\u2642\ufe0f"] = ":man_with_veil_tone1:",
["\U0001f470\U0001f3fc\u200d\u2642\ufe0f"] = ":man_with_veil_tone2:",
["\U0001f470\U0001f3fd\u200d\u2642\ufe0f"] = ":man_with_veil_tone3:",
["\U0001f470\U0001f3fe\u200d\u2642\ufe0f"] = ":man_with_veil_tone4:",
["\U0001f470\U0001f3ff\u200d\u2642\ufe0f"] = ":man_with_veil_tone5:",
["\U0001f9df\u200d\u2642\ufe0f"] = ":man_zombie:",
["\U0001f96d"] = ":mango:",
["\U0001f45e"] = ":mans_shoe:",
["\U0001f9bd"] = ":manual_wheelchair:",
["\U0001f5fa\ufe0f"] = ":map:",
["\U0001f5fa"] = ":map:",
["\U0001f341"] = ":maple_leaf:",
["\U0001f94b"] = ":martial_arts_uniform:",
["\U0001f637"] = ":mask:",
["\U0001f9c9"] = ":mate:",
["\U0001f356"] = ":meat_on_bone:",
["\U0001f9d1\u200d\U0001f527"] = ":mechanic:",
["\U0001f9d1\U0001f3fb\u200d\U0001f527"] = ":mechanic_tone1:",
["\U0001f9d1\U0001f3fc\u200d\U0001f527"] = ":mechanic_tone2:",
["\U0001f9d1\U0001f3fd\u200d\U0001f527"] = ":mechanic_tone3:",
["\U0001f9d1\U0001f3fe\u200d\U0001f527"] = ":mechanic_tone4:",
["\U0001f9d1\U0001f3ff\u200d\U0001f527"] = ":mechanic_tone5:",
["\U0001f9be"] = ":mechanical_arm:",
["\U0001f9bf"] = ":mechanical_leg:",
["\U0001f3c5"] = ":medal:",
["\u2695\ufe0f"] = ":medical_symbol:",
["\u2695"] = ":medical_symbol:",
["\U0001f4e3"] = ":mega:",
["\U0001f348"] = ":melon:",
["\U0001f46f\u200d\u2642\ufe0f"] = ":men_with_bunny_ears_partying:",
["\U0001f93c\u200d\u2642\ufe0f"] = ":men_wrestling:",
["\U0001f54e"] = ":menorah:",
["\U0001f6b9"] = ":mens:",
["\U0001f9dc\u200d\u2640\ufe0f"] = ":mermaid:",
["\U0001f9dc\U0001f3fb\u200d\u2640\ufe0f"] = ":mermaid_tone1:",
["\U0001f9dc\U0001f3fc\u200d\u2640\ufe0f"] = ":mermaid_tone2:",
["\U0001f9dc\U0001f3fd\u200d\u2640\ufe0f"] = ":mermaid_tone3:",
["\U0001f9dc\U0001f3fe\u200d\u2640\ufe0f"] = ":mermaid_tone4:",
["\U0001f9dc\U0001f3ff\u200d\u2640\ufe0f"] = ":mermaid_tone5:",
["\U0001f9dc\u200d\u2642\ufe0f"] = ":merman:",
["\U0001f9dc\U0001f3fb\u200d\u2642\ufe0f"] = ":merman_tone1:",
["\U0001f9dc\U0001f3fc\u200d\u2642\ufe0f"] = ":merman_tone2:",
["\U0001f9dc\U0001f3fd\u200d\u2642\ufe0f"] = ":merman_tone3:",
["\U0001f9dc\U0001f3fe\u200d\u2642\ufe0f"] = ":merman_tone4:",
["\U0001f9dc\U0001f3ff\u200d\u2642\ufe0f"] = ":merman_tone5:",
["\U0001f9dc"] = ":merperson:",
["\U0001f9dc\U0001f3fb"] = ":merperson_tone1:",
["\U0001f9dc\U0001f3fc"] = ":merperson_tone2:",
["\U0001f9dc\U0001f3fd"] = ":merperson_tone3:",
["\U0001f9dc\U0001f3fe"] = ":merperson_tone4:",
["\U0001f9dc\U0001f3ff"] = ":merperson_tone5:",
["\U0001f918"] = ":metal:",
["\U0001f918\U0001f3fb"] = ":metal_tone1:",
["\U0001f918\U0001f3fc"] = ":metal_tone2:",
["\U0001f918\U0001f3fd"] = ":metal_tone3:",
["\U0001f918\U0001f3fe"] = ":metal_tone4:",
["\U0001f918\U0001f3ff"] = ":metal_tone5:",
["\U0001f687"] = ":metro:",
["\U0001f9a0"] = ":microbe:",
["\U0001f3a4"] = ":microphone:",
["\U0001f399\ufe0f"] = ":microphone2:",
["\U0001f399"] = ":microphone2:",
["\U0001f52c"] = ":microscope:",
["\U0001f595"] = ":middle_finger:",
["\U0001f595\U0001f3fb"] = ":middle_finger_tone1:",
["\U0001f595\U0001f3fc"] = ":middle_finger_tone2:",
["\U0001f595\U0001f3fd"] = ":middle_finger_tone3:",
["\U0001f595\U0001f3fe"] = ":middle_finger_tone4:",
["\U0001f595\U0001f3ff"] = ":middle_finger_tone5:",
["\U0001fa96"] = ":military_helmet:",
["\U0001f396\ufe0f"] = ":military_medal:",
["\U0001f396"] = ":military_medal:",
["\U0001f95b"] = ":milk:",
["\U0001f30c"] = ":milky_way:",
["\U0001f690"] = ":minibus:",
["\U0001f4bd"] = ":minidisc:",
["\U0001fa9e"] = ":mirror:",
["\U0001f4f1"] = ":mobile_phone:",
["\U0001f4f4"] = ":mobile_phone_off:",
["\U0001f911"] = ":money_mouth:",
["\U0001f4b8"] = ":money_with_wings:",
["\U0001f4b0"] = ":moneybag:",
["\U0001f412"] = ":monkey:",
["\U0001f435"] = ":monkey_face:",
["\U0001f69d"] = ":monorail:",
["\U0001f96e"] = ":moon_cake:",
["\U0001f393"] = ":mortar_board:",
["\U0001f54c"] = ":mosque:",
["\U0001f99f"] = ":mosquito:",
["\U0001f6f5"] = ":motor_scooter:",
["\U0001f6e5\ufe0f"] = ":motorboat:",
["\U0001f6e5"] = ":motorboat:",
["\U0001f3cd\ufe0f"] = ":motorcycle:",
["\U0001f3cd"] = ":motorcycle:",
["\U0001f9bc"] = ":motorized_wheelchair:",
["\U0001f6e3\ufe0f"] = ":motorway:",
["\U0001f6e3"] = ":motorway:",
["\U0001f5fb"] = ":mount_fuji:",
["\u26f0\ufe0f"] = ":mountain:",
["\u26f0"] = ":mountain:",
["\U0001f6a0"] = ":mountain_cableway:",
["\U0001f69e"] = ":mountain_railway:",
["\U0001f3d4\ufe0f"] = ":mountain_snow:",
["\U0001f3d4"] = ":mountain_snow:",
["\U0001f42d"] = ":mouse:",
["\U0001f5b1\ufe0f"] = ":mouse_three_button:",
["\U0001f5b1"] = ":mouse_three_button:",
["\U0001faa4"] = ":mouse_trap:",
["\U0001f401"] = ":mouse2:",
["\U0001f3a5"] = ":movie_camera:",
["\U0001f5ff"] = ":moyai:",
["\U0001f936"] = ":mrs_claus:",
["\U0001f936\U0001f3fb"] = ":mrs_claus_tone1:",
["\U0001f936\U0001f3fc"] = ":mrs_claus_tone2:",
["\U0001f936\U0001f3fd"] = ":mrs_claus_tone3:",
["\U0001f936\U0001f3fe"] = ":mrs_claus_tone4:",
["\U0001f936\U0001f3ff"] = ":mrs_claus_tone5:",
["\U0001f4aa"] = ":muscle:",
["\U0001f4aa\U0001f3fb"] = ":muscle_tone1:",
["\U0001f4aa\U0001f3fc"] = ":muscle_tone2:",
["\U0001f4aa\U0001f3fd"] = ":muscle_tone3:",
["\U0001f4aa\U0001f3fe"] = ":muscle_tone4:",
["\U0001f4aa\U0001f3ff"] = ":muscle_tone5:",
["\U0001f344"] = ":mushroom:",
["\U0001f3b9"] = ":musical_keyboard:",
["\U0001f3b5"] = ":musical_note:",
["\U0001f3bc"] = ":musical_score:",
["\U0001f507"] = ":mute:",
["\U0001f9d1\u200d\U0001f384"] = ":mx_claus:",
["\U0001f9d1\U0001f3fb\u200d\U0001f384"] = ":mx_claus_tone1:",
["\U0001f9d1\U0001f3fc\u200d\U0001f384"] = ":mx_claus_tone2:",
["\U0001f9d1\U0001f3fd\u200d\U0001f384"] = ":mx_claus_tone3:",
["\U0001f9d1\U0001f3fe\u200d\U0001f384"] = ":mx_claus_tone4:",
["\U0001f9d1\U0001f3ff\u200d\U0001f384"] = ":mx_claus_tone5:",
["\U0001f485"] = ":nail_care:",
["\U0001f485\U0001f3fb"] = ":nail_care_tone1:",
["\U0001f485\U0001f3fc"] = ":nail_care_tone2:",
["\U0001f485\U0001f3fd"] = ":nail_care_tone3:",
["\U0001f485\U0001f3fe"] = ":nail_care_tone4:",
["\U0001f485\U0001f3ff"] = ":nail_care_tone5:",
["\U0001f4db"] = ":name_badge:",
["\U0001f922"] = ":nauseated_face:",
["\U0001f9ff"] = ":nazar_amulet:",
["\U0001f454"] = ":necktie:",
["\u274e"] = ":negative_squared_cross_mark:",
["\U0001f913"] = ":nerd:",
["\U0001fa86"] = ":nesting_dolls:",
["\U0001f610"] = ":neutral_face:",
["\U0001f195"] = ":new:",
["\U0001f311"] = ":new_moon:",
["\U0001f31a"] = ":new_moon_with_face:",
["\U0001f4f0"] = ":newspaper:",
["\U0001f5de\ufe0f"] = ":newspaper2:",
["\U0001f5de"] = ":newspaper2:",
["\U0001f196"] = ":ng:",
["\U0001f303"] = ":night_with_stars:",
["\u0039\ufe0f\u20e3"] = ":nine:",
["\u0039\u20e3"] = ":nine:",
["\U0001f977"] = ":ninja:",
["\U0001f977\U0001f3fb"] = ":ninja_tone1:",
["\U0001f977\U0001f3fc"] = ":ninja_tone2:",
["\U0001f977\U0001f3fd"] = ":ninja_tone3:",
["\U0001f977\U0001f3fe"] = ":ninja_tone4:",
["\U0001f977\U0001f3ff"] = ":ninja_tone5:",
["\U0001f515"] = ":no_bell:",
["\U0001f6b3"] = ":no_bicycles:",
["\u26d4"] = ":no_entry:",
["\U0001f6ab"] = ":no_entry_sign:",
["\U0001f4f5"] = ":no_mobile_phones:",
["\U0001f636"] = ":no_mouth:",
["\U0001f6b7"] = ":no_pedestrians:",
["\U0001f6ad"] = ":no_smoking:",
["\U0001f6b1"] = ":non_potable_water:",
["\U0001f443"] = ":nose:",
["\U0001f443\U0001f3fb"] = ":nose_tone1:",
["\U0001f443\U0001f3fc"] = ":nose_tone2:",
["\U0001f443\U0001f3fd"] = ":nose_tone3:",
["\U0001f443\U0001f3fe"] = ":nose_tone4:",
["\U0001f443\U0001f3ff"] = ":nose_tone5:",
["\U0001f4d3"] = ":notebook:",
["\U0001f4d4"] = ":notebook_with_decorative_cover:",
["\U0001f5d2\ufe0f"] = ":notepad_spiral:",
["\U0001f5d2"] = ":notepad_spiral:",
["\U0001f3b6"] = ":notes:",
["\U0001f529"] = ":nut_and_bolt:",
["\u2b55"] = ":o:",
["\U0001f17e\ufe0f"] = ":o2:",
["\U0001f17e"] = ":o2:",
["\U0001f30a"] = ":ocean:",
["\U0001f6d1"] = ":octagonal_sign:",
["\U0001f419"] = ":octopus:",
["\U0001f362"] = ":oden:",
["\U0001f3e2"] = ":office:",
["\U0001f9d1\u200d\U0001f4bc"] = ":office_worker:",
["\U0001f9d1\U0001f3fb\u200d\U0001f4bc"] = ":office_worker_tone1:",
["\U0001f9d1\U0001f3fc\u200d\U0001f4bc"] = ":office_worker_tone2:",
["\U0001f9d1\U0001f3fd\u200d\U0001f4bc"] = ":office_worker_tone3:",
["\U0001f9d1\U0001f3fe\u200d\U0001f4bc"] = ":office_worker_tone4:",
["\U0001f9d1\U0001f3ff\u200d\U0001f4bc"] = ":office_worker_tone5:",
["\U0001f6e2\ufe0f"] = ":oil:",
["\U0001f6e2"] = ":oil:",
["\U0001f197"] = ":ok:",
["\U0001f44c"] = ":ok_hand:",
["\U0001f44c\U0001f3fb"] = ":ok_hand_tone1:",
["\U0001f44c\U0001f3fc"] = ":ok_hand_tone2:",
["\U0001f44c\U0001f3fd"] = ":ok_hand_tone3:",
["\U0001f44c\U0001f3fe"] = ":ok_hand_tone4:",
["\U0001f44c\U0001f3ff"] = ":ok_hand_tone5:",
["\U0001f9d3"] = ":older_adult:",
["\U0001f9d3\U0001f3fb"] = ":older_adult_tone1:",
["\U0001f9d3\U0001f3fc"] = ":older_adult_tone2:",
["\U0001f9d3\U0001f3fd"] = ":older_adult_tone3:",
["\U0001f9d3\U0001f3fe"] = ":older_adult_tone4:",
["\U0001f9d3\U0001f3ff"] = ":older_adult_tone5:",
["\U0001f474"] = ":older_man:",
["\U0001f474\U0001f3fb"] = ":older_man_tone1:",
["\U0001f474\U0001f3fc"] = ":older_man_tone2:",
["\U0001f474\U0001f3fd"] = ":older_man_tone3:",
["\U0001f474\U0001f3fe"] = ":older_man_tone4:",
["\U0001f474\U0001f3ff"] = ":older_man_tone5:",
["\U0001f475"] = ":older_woman:",
["\U0001f475\U0001f3fb"] = ":older_woman_tone1:",
["\U0001f475\U0001f3fc"] = ":older_woman_tone2:",
["\U0001f475\U0001f3fd"] = ":older_woman_tone3:",
["\U0001f475\U0001f3fe"] = ":older_woman_tone4:",
["\U0001f475\U0001f3ff"] = ":older_woman_tone5:",
["\U0001fad2"] = ":olive:",
["\U0001f549\ufe0f"] = ":om_symbol:",
["\U0001f549"] = ":om_symbol:",
["\U0001f51b"] = ":on:",
["\U0001f698"] = ":oncoming_automobile:",
["\U0001f68d"] = ":oncoming_bus:",
["\U0001f694"] = ":oncoming_police_car:",
["\U0001f696"] = ":oncoming_taxi:",
["\u0031\ufe0f\u20e3"] = ":one:",
["\u0031\u20e3"] = ":one:",
["\U0001fa71"] = ":one_piece_swimsuit:",
["\U0001f9c5"] = ":onion:",
["\U0001f4c2"] = ":open_file_folder:",
["\U0001f450"] = ":open_hands:",
["\U0001f450\U0001f3fb"] = ":open_hands_tone1:",
["\U0001f450\U0001f3fc"] = ":open_hands_tone2:",
["\U0001f450\U0001f3fd"] = ":open_hands_tone3:",
["\U0001f450\U0001f3fe"] = ":open_hands_tone4:",
["\U0001f450\U0001f3ff"] = ":open_hands_tone5:",
["\U0001f62e"] = ":open_mouth:",
["\u26ce"] = ":ophiuchus:",
["\U0001f4d9"] = ":orange_book:",
["\U0001f7e0"] = ":orange_circle:",
["\U0001f9e1"] = ":orange_heart:",
["\U0001f7e7"] = ":orange_square:",
["\U0001f9a7"] = ":orangutan:",
["\u2626\ufe0f"] = ":orthodox_cross:",
["\u2626"] = ":orthodox_cross:",
["\U0001f9a6"] = ":otter:",
["\U0001f4e4"] = ":outbox_tray:",
["\U0001f989"] = ":owl:",
["\U0001f402"] = ":ox:",
["\U0001f9aa"] = ":oyster:",
["\U0001f4e6"] = ":package:",
["\U0001f4c4"] = ":page_facing_up:",
["\U0001f4c3"] = ":page_with_curl:",
["\U0001f4df"] = ":pager:",
["\U0001f58c\ufe0f"] = ":paintbrush:",
["\U0001f58c"] = ":paintbrush:",
["\U0001f334"] = ":palm_tree:",
["\U0001f932"] = ":palms_up_together:",
["\U0001f932\U0001f3fb"] = ":palms_up_together_tone1:",
["\U0001f932\U0001f3fc"] = ":palms_up_together_tone2:",
["\U0001f932\U0001f3fd"] = ":palms_up_together_tone3:",
["\U0001f932\U0001f3fe"] = ":palms_up_together_tone4:",
["\U0001f932\U0001f3ff"] = ":palms_up_together_tone5:",
["\U0001f95e"] = ":pancakes:",
["\U0001f43c"] = ":panda_face:",
["\U0001f4ce"] = ":paperclip:",
["\U0001f587\ufe0f"] = ":paperclips:",
["\U0001f587"] = ":paperclips:",
["\U0001fa82"] = ":parachute:",
["\U0001f3de\ufe0f"] = ":park:",
["\U0001f3de"] = ":park:",
["\U0001f17f\ufe0f"] = ":parking:",
["\U0001f17f"] = ":parking:",
["\U0001f99c"] = ":parrot:",
["\u303d\ufe0f"] = ":part_alternation_mark:",
["\u303d"] = ":part_alternation_mark:",
["\u26c5"] = ":partly_sunny:",
["\U0001f973"] = ":partying_face:",
["\U0001f6c2"] = ":passport_control:",
["\u23f8\ufe0f"] = ":pause_button:",
["\u23f8"] = ":pause_button:",
["\u262e\ufe0f"] = ":peace:",
["\u262e"] = ":peace:",
["\U0001f351"] = ":peach:",
["\U0001f99a"] = ":peacock:",
["\U0001f95c"] = ":peanuts:",
["\U0001f350"] = ":pear:",
["\U0001f58a\ufe0f"] = ":pen_ballpoint:",
["\U0001f58a"] = ":pen_ballpoint:",
["\U0001f58b\ufe0f"] = ":pen_fountain:",
["\U0001f58b"] = ":pen_fountain:",
["\U0001f4dd"] = ":pencil:",
["\u270f\ufe0f"] = ":pencil2:",
["\u270f"] = ":pencil2:",
["\U0001f427"] = ":penguin:",
["\U0001f614"] = ":pensive:",
["\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1"] = ":people_holding_hands_tone5_tone4:",
["\U0001fac2"] = ":people_hugging:",
["\U0001f46f"] = ":people_with_bunny_ears_partying:",
["\U0001f93c"] = ":people_wrestling:",
["\U0001f3ad"] = ":performing_arts:",
["\U0001f623"] = ":persevere:",
["\U0001f9d1\u200d\U0001f9b2"] = ":person_bald:",
["\U0001f6b4"] = ":person_biking:",
["\U0001f6b4\U0001f3fb"] = ":person_biking_tone1:",
["\U0001f6b4\U0001f3fc"] = ":person_biking_tone2:",
["\U0001f6b4\U0001f3fd"] = ":person_biking_tone3:",
["\U0001f6b4\U0001f3fe"] = ":person_biking_tone4:",
["\U0001f6b4\U0001f3ff"] = ":person_biking_tone5:",
["\u26f9\ufe0f"] = ":person_bouncing_ball:",
["\u26f9"] = ":person_bouncing_ball:",
["\u26f9\U0001f3fb"] = ":person_bouncing_ball_tone1:",
["\u26f9\U0001f3fc"] = ":person_bouncing_ball_tone2:",
["\u26f9\U0001f3fd"] = ":person_bouncing_ball_tone3:",
["\u26f9\U0001f3fe"] = ":person_bouncing_ball_tone4:",
["\u26f9\U0001f3ff"] = ":person_bouncing_ball_tone5:",
["\U0001f647"] = ":person_bowing:",
["\U0001f647\U0001f3fb"] = ":person_bowing_tone1:",
["\U0001f647\U0001f3fc"] = ":person_bowing_tone2:",
["\U0001f647\U0001f3fd"] = ":person_bowing_tone3:",
["\U0001f647\U0001f3fe"] = ":person_bowing_tone4:",
["\U0001f647\U0001f3ff"] = ":person_bowing_tone5:",
["\U0001f9d7"] = ":person_climbing:",
["\U0001f9d7\U0001f3fb"] = ":person_climbing_tone1:",
["\U0001f9d7\U0001f3fc"] = ":person_climbing_tone2:",
["\U0001f9d7\U0001f3fd"] = ":person_climbing_tone3:",
["\U0001f9d7\U0001f3fe"] = ":person_climbing_tone4:",
["\U0001f9d7\U0001f3ff"] = ":person_climbing_tone5:",
["\U0001f9d1\u200d\U0001f9b1"] = ":person_curly_hair:",
["\U0001f938"] = ":person_doing_cartwheel:",
["\U0001f938\U0001f3fb"] = ":person_doing_cartwheel_tone1:",
["\U0001f938\U0001f3fc"] = ":person_doing_cartwheel_tone2:",
["\U0001f938\U0001f3fd"] = ":person_doing_cartwheel_tone3:",
["\U0001f938\U0001f3fe"] = ":person_doing_cartwheel_tone4:",
["\U0001f938\U0001f3ff"] = ":person_doing_cartwheel_tone5:",
["\U0001f926"] = ":person_facepalming:",
["\U0001f926\U0001f3fb"] = ":person_facepalming_tone1:",
["\U0001f926\U0001f3fc"] = ":person_facepalming_tone2:",
["\U0001f926\U0001f3fd"] = ":person_facepalming_tone3:",
["\U0001f926\U0001f3fe"] = ":person_facepalming_tone4:",
["\U0001f926\U0001f3ff"] = ":person_facepalming_tone5:",
["\U0001f9d1\u200d\U0001f37c"] = ":person_feeding_baby:",
["\U0001f9d1\U0001f3fb\u200d\U0001f37c"] = ":person_feeding_baby_tone1:",
["\U0001f9d1\U0001f3fc\u200d\U0001f37c"] = ":person_feeding_baby_tone2:",
["\U0001f9d1\U0001f3fd\u200d\U0001f37c"] = ":person_feeding_baby_tone3:",
["\U0001f9d1\U0001f3fe\u200d\U0001f37c"] = ":person_feeding_baby_tone4:",
["\U0001f9d1\U0001f3ff\u200d\U0001f37c"] = ":person_feeding_baby_tone5:",
["\U0001f93a"] = ":person_fencing:",
["\U0001f64d"] = ":person_frowning:",
["\U0001f64d\U0001f3fb"] = ":person_frowning_tone1:",
["\U0001f64d\U0001f3fc"] = ":person_frowning_tone2:",
["\U0001f64d\U0001f3fd"] = ":person_frowning_tone3:",
["\U0001f64d\U0001f3fe"] = ":person_frowning_tone4:",
["\U0001f64d\U0001f3ff"] = ":person_frowning_tone5:",
["\U0001f645"] = ":person_gesturing_no:",
["\U0001f645\U0001f3fb"] = ":person_gesturing_no_tone1:",
["\U0001f645\U0001f3fc"] = ":person_gesturing_no_tone2:",
["\U0001f645\U0001f3fd"] = ":person_gesturing_no_tone3:",
["\U0001f645\U0001f3fe"] = ":person_gesturing_no_tone4:",
["\U0001f645\U0001f3ff"] = ":person_gesturing_no_tone5:",
["\U0001f646"] = ":person_gesturing_ok:",
["\U0001f646\U0001f3fb"] = ":person_gesturing_ok_tone1:",
["\U0001f646\U0001f3fc"] = ":person_gesturing_ok_tone2:",
["\U0001f646\U0001f3fd"] = ":person_gesturing_ok_tone3:",
["\U0001f646\U0001f3fe"] = ":person_gesturing_ok_tone4:",
["\U0001f646\U0001f3ff"] = ":person_gesturing_ok_tone5:",
["\U0001f487"] = ":person_getting_haircut:",
["\U0001f487\U0001f3fb"] = ":person_getting_haircut_tone1:",
["\U0001f487\U0001f3fc"] = ":person_getting_haircut_tone2:",
["\U0001f487\U0001f3fd"] = ":person_getting_haircut_tone3:",
["\U0001f487\U0001f3fe"] = ":person_getting_haircut_tone4:",
["\U0001f487\U0001f3ff"] = ":person_getting_haircut_tone5:",
["\U0001f486"] = ":person_getting_massage:",
["\U0001f486\U0001f3fb"] = ":person_getting_massage_tone1:",
["\U0001f486\U0001f3fc"] = ":person_getting_massage_tone2:",
["\U0001f486\U0001f3fd"] = ":person_getting_massage_tone3:",
["\U0001f486\U0001f3fe"] = ":person_getting_massage_tone4:",
["\U0001f486\U0001f3ff"] = ":person_getting_massage_tone5:",
["\U0001f3cc\ufe0f"] = ":person_golfing:",
["\U0001f3cc"] = ":person_golfing:",
["\U0001f3cc\U0001f3fb"] = ":person_golfing_tone1:",
["\U0001f3cc\U0001f3fc"] = ":person_golfing_tone2:",
["\U0001f3cc\U0001f3fd"] = ":person_golfing_tone3:",
["\U0001f3cc\U0001f3fe"] = ":person_golfing_tone4:",
["\U0001f3cc\U0001f3ff"] = ":person_golfing_tone5:",
["\U0001f6cc\U0001f3fb"] = ":person_in_bed_tone1:",
["\U0001f6cc\U0001f3fc"] = ":person_in_bed_tone2:",
["\U0001f6cc\U0001f3fd"] = ":person_in_bed_tone3:",
["\U0001f6cc\U0001f3fe"] = ":person_in_bed_tone4:",
["\U0001f6cc\U0001f3ff"] = ":person_in_bed_tone5:",
["\U0001f9d8"] = ":person_in_lotus_position:",
["\U0001f9d8\U0001f3fb"] = ":person_in_lotus_position_tone1:",
["\U0001f9d8\U0001f3fc"] = ":person_in_lotus_position_tone2:",
["\U0001f9d8\U0001f3fd"] = ":person_in_lotus_position_tone3:",
["\U0001f9d8\U0001f3fe"] = ":person_in_lotus_position_tone4:",
["\U0001f9d8\U0001f3ff"] = ":person_in_lotus_position_tone5:",
["\U0001f9d1\u200d\U0001f9bd"] = ":person_in_manual_wheelchair:",
["\U0001f9d1\U0001f3fb\u200d\U0001f9bd"] = ":person_in_manual_wheelchair_tone1:",
["\U0001f9d1\U0001f3fc\u200d\U0001f9bd"] = ":person_in_manual_wheelchair_tone2:",
["\U0001f9d1\U0001f3fd\u200d\U0001f9bd"] = ":person_in_manual_wheelchair_tone3:",
["\U0001f9d1\U0001f3fe\u200d\U0001f9bd"] = ":person_in_manual_wheelchair_tone4:",
["\U0001f9d1\U0001f3ff\u200d\U0001f9bd"] = ":person_in_manual_wheelchair_tone5:",
["\U0001f9d1\u200d\U0001f9bc"] = ":person_in_motorized_wheelchair:",
["\U0001f9d1\U0001f3fb\u200d\U0001f9bc"] = ":person_in_motorized_wheelchair_tone1:",
["\U0001f9d1\U0001f3fc\u200d\U0001f9bc"] = ":person_in_motorized_wheelchair_tone2:",
["\U0001f9d1\U0001f3fd\u200d\U0001f9bc"] = ":person_in_motorized_wheelchair_tone3:",
["\U0001f9d1\U0001f3fe\u200d\U0001f9bc"] = ":person_in_motorized_wheelchair_tone4:",
["\U0001f9d1\U0001f3ff\u200d\U0001f9bc"] = ":person_in_motorized_wheelchair_tone5:",
["\U0001f9d6"] = ":person_in_steamy_room:",
["\U0001f9d6\U0001f3fb"] = ":person_in_steamy_room_tone1:",
["\U0001f9d6\U0001f3fc"] = ":person_in_steamy_room_tone2:",
["\U0001f9d6\U0001f3fd"] = ":person_in_steamy_room_tone3:",
["\U0001f9d6\U0001f3fe"] = ":person_in_steamy_room_tone4:",
["\U0001f9d6\U0001f3ff"] = ":person_in_steamy_room_tone5:",
["\U0001f935"] = ":person_in_tuxedo:",
["\U0001f935\U0001f3fb"] = ":person_in_tuxedo_tone1:",
["\U0001f935\U0001f3fc"] = ":person_in_tuxedo_tone2:",
["\U0001f935\U0001f3fd"] = ":person_in_tuxedo_tone3:",
["\U0001f935\U0001f3fe"] = ":person_in_tuxedo_tone4:",
["\U0001f935\U0001f3ff"] = ":person_in_tuxedo_tone5:",
["\U0001f939"] = ":person_juggling:",
["\U0001f939\U0001f3fb"] = ":person_juggling_tone1:",
["\U0001f939\U0001f3fc"] = ":person_juggling_tone2:",
["\U0001f939\U0001f3fd"] = ":person_juggling_tone3:",
["\U0001f939\U0001f3fe"] = ":person_juggling_tone4:",
["\U0001f939\U0001f3ff"] = ":person_juggling_tone5:",
["\U0001f9ce"] = ":person_kneeling:",
["\U0001f9ce\U0001f3fb"] = ":person_kneeling_tone1:",
["\U0001f9ce\U0001f3fc"] = ":person_kneeling_tone2:",
["\U0001f9ce\U0001f3fd"] = ":person_kneeling_tone3:",
["\U0001f9ce\U0001f3fe"] = ":person_kneeling_tone4:",
["\U0001f9ce\U0001f3ff"] = ":person_kneeling_tone5:",
["\U0001f3cb\ufe0f"] = ":person_lifting_weights:",
["\U0001f3cb"] = ":person_lifting_weights:",
["\U0001f3cb\U0001f3fb"] = ":person_lifting_weights_tone1:",
["\U0001f3cb\U0001f3fc"] = ":person_lifting_weights_tone2:",
["\U0001f3cb\U0001f3fd"] = ":person_lifting_weights_tone3:",
["\U0001f3cb\U0001f3fe"] = ":person_lifting_weights_tone4:",
["\U0001f3cb\U0001f3ff"] = ":person_lifting_weights_tone5:",
["\U0001f6b5"] = ":person_mountain_biking:",
["\U0001f6b5\U0001f3fb"] = ":person_mountain_biking_tone1:",
["\U0001f6b5\U0001f3fc"] = ":person_mountain_biking_tone2:",
["\U0001f6b5\U0001f3fd"] = ":person_mountain_biking_tone3:",
["\U0001f6b5\U0001f3fe"] = ":person_mountain_biking_tone4:",
["\U0001f6b5\U0001f3ff"] = ":person_mountain_biking_tone5:",
["\U0001f93e"] = ":person_playing_handball:",
["\U0001f93e\U0001f3fb"] = ":person_playing_handball_tone1:",
["\U0001f93e\U0001f3fc"] = ":person_playing_handball_tone2:",
["\U0001f93e\U0001f3fd"] = ":person_playing_handball_tone3:",
["\U0001f93e\U0001f3fe"] = ":person_playing_handball_tone4:",
["\U0001f93e\U0001f3ff"] = ":person_playing_handball_tone5:",
["\U0001f93d"] = ":person_playing_water_polo:",
["\U0001f93d\U0001f3fb"] = ":person_playing_water_polo_tone1:",
["\U0001f93d\U0001f3fc"] = ":person_playing_water_polo_tone2:",
["\U0001f93d\U0001f3fd"] = ":person_playing_water_polo_tone3:",
["\U0001f93d\U0001f3fe"] = ":person_playing_water_polo_tone4:",
["\U0001f93d\U0001f3ff"] = ":person_playing_water_polo_tone5:",
["\U0001f64e"] = ":person_pouting:",
["\U0001f64e\U0001f3fb"] = ":person_pouting_tone1:",
["\U0001f64e\U0001f3fc"] = ":person_pouting_tone2:",
["\U0001f64e\U0001f3fd"] = ":person_pouting_tone3:",
["\U0001f64e\U0001f3fe"] = ":person_pouting_tone4:",
["\U0001f64e\U0001f3ff"] = ":person_pouting_tone5:",
["\U0001f64b"] = ":person_raising_hand:",
["\U0001f64b\U0001f3fb"] = ":person_raising_hand_tone1:",
["\U0001f64b\U0001f3fc"] = ":person_raising_hand_tone2:",
["\U0001f64b\U0001f3fd"] = ":person_raising_hand_tone3:",
["\U0001f64b\U0001f3fe"] = ":person_raising_hand_tone4:",
["\U0001f64b\U0001f3ff"] = ":person_raising_hand_tone5:",
["\U0001f9d1\u200d\U0001f9b0"] = ":person_red_hair:",
["\U0001f6a3"] = ":person_rowing_boat:",
["\U0001f6a3\U0001f3fb"] = ":person_rowing_boat_tone1:",
["\U0001f6a3\U0001f3fc"] = ":person_rowing_boat_tone2:",
["\U0001f6a3\U0001f3fd"] = ":person_rowing_boat_tone3:",
["\U0001f6a3\U0001f3fe"] = ":person_rowing_boat_tone4:",
["\U0001f6a3\U0001f3ff"] = ":person_rowing_boat_tone5:",
["\U0001f3c3"] = ":person_running:",
["\U0001f3c3\U0001f3fb"] = ":person_running_tone1:",
["\U0001f3c3\U0001f3fc"] = ":person_running_tone2:",
["\U0001f3c3\U0001f3fd"] = ":person_running_tone3:",
["\U0001f3c3\U0001f3fe"] = ":person_running_tone4:",
["\U0001f3c3\U0001f3ff"] = ":person_running_tone5:",
["\U0001f937"] = ":person_shrugging:",
["\U0001f937\U0001f3fb"] = ":person_shrugging_tone1:",
["\U0001f937\U0001f3fc"] = ":person_shrugging_tone2:",
["\U0001f937\U0001f3fd"] = ":person_shrugging_tone3:",
["\U0001f937\U0001f3fe"] = ":person_shrugging_tone4:",
["\U0001f937\U0001f3ff"] = ":person_shrugging_tone5:",
["\U0001f9cd"] = ":person_standing:",
["\U0001f9cd\U0001f3fb"] = ":person_standing_tone1:",
["\U0001f9cd\U0001f3fc"] = ":person_standing_tone2:",
["\U0001f9cd\U0001f3fd"] = ":person_standing_tone3:",
["\U0001f9cd\U0001f3fe"] = ":person_standing_tone4:",
["\U0001f9cd\U0001f3ff"] = ":person_standing_tone5:",
["\U0001f3c4"] = ":person_surfing:",
["\U0001f3c4\U0001f3fb"] = ":person_surfing_tone1:",
["\U0001f3c4\U0001f3fc"] = ":person_surfing_tone2:",
["\U0001f3c4\U0001f3fd"] = ":person_surfing_tone3:",
["\U0001f3c4\U0001f3fe"] = ":person_surfing_tone4:",
["\U0001f3c4\U0001f3ff"] = ":person_surfing_tone5:",
["\U0001f3ca"] = ":person_swimming:",
["\U0001f3ca\U0001f3fb"] = ":person_swimming_tone1:",
["\U0001f3ca\U0001f3fc"] = ":person_swimming_tone2:",
["\U0001f3ca\U0001f3fd"] = ":person_swimming_tone3:",
["\U0001f3ca\U0001f3fe"] = ":person_swimming_tone4:",
["\U0001f3ca\U0001f3ff"] = ":person_swimming_tone5:",
["\U0001f481"] = ":person_tipping_hand:",
["\U0001f481\U0001f3fb"] = ":person_tipping_hand_tone1:",
["\U0001f481\U0001f3fc"] = ":person_tipping_hand_tone2:",
["\U0001f481\U0001f3fd"] = ":person_tipping_hand_tone3:",
["\U0001f481\U0001f3fe"] = ":person_tipping_hand_tone4:",
["\U0001f481\U0001f3ff"] = ":person_tipping_hand_tone5:",
["\U0001f9d1\U0001f3fb\u200d\U0001f9b2"] = ":person_tone1_bald:",
["\U0001f9d1\U0001f3fb\u200d\U0001f9b1"] = ":person_tone1_curly_hair:",
["\U0001f9d1\U0001f3fb\u200d\U0001f9b0"] = ":person_tone1_red_hair:",
["\U0001f9d1\U0001f3fb\u200d\U0001f9b3"] = ":person_tone1_white_hair:",
["\U0001f9d1\U0001f3fc\u200d\U0001f9b2"] = ":person_tone2_bald:",
["\U0001f9d1\U0001f3fc\u200d\U0001f9b1"] = ":person_tone2_curly_hair:",
["\U0001f9d1\U0001f3fc\u200d\U0001f9b0"] = ":person_tone2_red_hair:",
["\U0001f9d1\U0001f3fc\u200d\U0001f9b3"] = ":person_tone2_white_hair:",
["\U0001f9d1\U0001f3fd\u200d\U0001f9b2"] = ":person_tone3_bald:",
["\U0001f9d1\U0001f3fd\u200d\U0001f9b1"] = ":person_tone3_curly_hair:",
["\U0001f9d1\U0001f3fd\u200d\U0001f9b0"] = ":person_tone3_red_hair:",
["\U0001f9d1\U0001f3fd\u200d\U0001f9b3"] = ":person_tone3_white_hair:",
["\U0001f9d1\U0001f3fe\u200d\U0001f9b2"] = ":person_tone4_bald:",
["\U0001f9d1\U0001f3fe\u200d\U0001f9b1"] = ":person_tone4_curly_hair:",
["\U0001f9d1\U0001f3fe\u200d\U0001f9b0"] = ":person_tone4_red_hair:",
["\U0001f9d1\U0001f3fe\u200d\U0001f9b3"] = ":person_tone4_white_hair:",
["\U0001f9d1\U0001f3ff\u200d\U0001f9b2"] = ":person_tone5_bald:",
["\U0001f9d1\U0001f3ff\u200d\U0001f9b1"] = ":person_tone5_curly_hair:",
["\U0001f9d1\U0001f3ff\u200d\U0001f9b0"] = ":person_tone5_red_hair:",
["\U0001f9d1\U0001f3ff\u200d\U0001f9b3"] = ":person_tone5_white_hair:",
["\U0001f6b6"] = ":person_walking:",
["\U0001f6b6\U0001f3fb"] = ":person_walking_tone1:",
["\U0001f6b6\U0001f3fc"] = ":person_walking_tone2:",
["\U0001f6b6\U0001f3fd"] = ":person_walking_tone3:",
["\U0001f6b6\U0001f3fe"] = ":person_walking_tone4:",
["\U0001f6b6\U0001f3ff"] = ":person_walking_tone5:",
["\U0001f473"] = ":person_wearing_turban:",
["\U0001f473\U0001f3fb"] = ":person_wearing_turban_tone1:",
["\U0001f473\U0001f3fc"] = ":person_wearing_turban_tone2:",
["\U0001f473\U0001f3fd"] = ":person_wearing_turban_tone3:",
["\U0001f473\U0001f3fe"] = ":person_wearing_turban_tone4:",
["\U0001f473\U0001f3ff"] = ":person_wearing_turban_tone5:",
["\U0001f9d1\u200d\U0001f9b3"] = ":person_white_hair:",
["\U0001f9d1\u200d\U0001f9af"] = ":person_with_probing_cane:",
["\U0001f9d1\U0001f3fb\u200d\U0001f9af"] = ":person_with_probing_cane_tone1:",
["\U0001f9d1\U0001f3fc\u200d\U0001f9af"] = ":person_with_probing_cane_tone2:",
["\U0001f9d1\U0001f3fd\u200d\U0001f9af"] = ":person_with_probing_cane_tone3:",
["\U0001f9d1\U0001f3fe\u200d\U0001f9af"] = ":person_with_probing_cane_tone4:",
["\U0001f9d1\U0001f3ff\u200d\U0001f9af"] = ":person_with_probing_cane_tone5:",
["\U0001f470"] = ":person_with_veil:",
["\U0001f470\U0001f3fb"] = ":person_with_veil_tone1:",
["\U0001f470\U0001f3fc"] = ":person_with_veil_tone2:",
["\U0001f470\U0001f3fd"] = ":person_with_veil_tone3:",
["\U0001f470\U0001f3fe"] = ":person_with_veil_tone4:",
["\U0001f470\U0001f3ff"] = ":person_with_veil_tone5:",
["\U0001f9eb"] = ":petri_dish:",
["\u26cf\ufe0f"] = ":pick:",
["\u26cf"] = ":pick:",
["\U0001f6fb"] = ":pickup_truck:",
["\U0001f967"] = ":pie:",
["\U0001f437"] = ":pig:",
["\U0001f43d"] = ":pig_nose:",
["\U0001f416"] = ":pig2:",
["\U0001f48a"] = ":pill:",
["\U0001f9d1\u200d\u2708\ufe0f"] = ":pilot:",
["\U0001f9d1\U0001f3fb\u200d\u2708\ufe0f"] = ":pilot_tone1:",
["\U0001f9d1\U0001f3fc\u200d\u2708\ufe0f"] = ":pilot_tone2:",
["\U0001f9d1\U0001f3fd\u200d\u2708\ufe0f"] = ":pilot_tone3:",
["\U0001f9d1\U0001f3fe\u200d\u2708\ufe0f"] = ":pilot_tone4:",
["\U0001f9d1\U0001f3ff\u200d\u2708\ufe0f"] = ":pilot_tone5:",
["\U0001fa85"] = ":piñata:",
["\U0001f90c"] = ":pinched_fingers:",
["\U0001f90c\U0001f3fb"] = ":pinched_fingers_tone1:",
["\U0001f90c\U0001f3fc"] = ":pinched_fingers_tone2:",
["\U0001f90c\U0001f3fd"] = ":pinched_fingers_tone3:",
["\U0001f90c\U0001f3fe"] = ":pinched_fingers_tone4:",
["\U0001f90c\U0001f3ff"] = ":pinched_fingers_tone5:",
["\U0001f90f"] = ":pinching_hand:",
["\U0001f90f\U0001f3fb"] = ":pinching_hand_tone1:",
["\U0001f90f\U0001f3fc"] = ":pinching_hand_tone2:",
["\U0001f90f\U0001f3fd"] = ":pinching_hand_tone3:",
["\U0001f90f\U0001f3fe"] = ":pinching_hand_tone4:",
["\U0001f90f\U0001f3ff"] = ":pinching_hand_tone5:",
["\U0001f34d"] = ":pineapple:",
["\U0001f3d3"] = ":ping_pong:",
["\U0001f3f4\u200d\u2620\ufe0f"] = ":pirate_flag:",
["\u2653"] = ":pisces:",
["\U0001f355"] = ":pizza:",
["\U0001faa7"] = ":placard:",
["\U0001f6d0"] = ":place_of_worship:",
["\u23ef\ufe0f"] = ":play_pause:",
["\u23ef"] = ":play_pause:",
["\U0001f97a"] = ":pleading_face:",
["\U0001faa0"] = ":plunger:",
["\U0001f447"] = ":point_down:",
["\U0001f447\U0001f3fb"] = ":point_down_tone1:",
["\U0001f447\U0001f3fc"] = ":point_down_tone2:",
["\U0001f447\U0001f3fd"] = ":point_down_tone3:",
["\U0001f447\U0001f3fe"] = ":point_down_tone4:",
["\U0001f447\U0001f3ff"] = ":point_down_tone5:",
["\U0001f448"] = ":point_left:",
["\U0001f448\U0001f3fb"] = ":point_left_tone1:",
["\U0001f448\U0001f3fc"] = ":point_left_tone2:",
["\U0001f448\U0001f3fd"] = ":point_left_tone3:",
["\U0001f448\U0001f3fe"] = ":point_left_tone4:",
["\U0001f448\U0001f3ff"] = ":point_left_tone5:",
["\U0001f449"] = ":point_right:",
["\U0001f449\U0001f3fb"] = ":point_right_tone1:",
["\U0001f449\U0001f3fc"] = ":point_right_tone2:",
["\U0001f449\U0001f3fd"] = ":point_right_tone3:",
["\U0001f449\U0001f3fe"] = ":point_right_tone4:",
["\U0001f449\U0001f3ff"] = ":point_right_tone5:",
["\u261d\ufe0f"] = ":point_up:",
["\u261d"] = ":point_up:",
["\U0001f446"] = ":point_up_2:",
["\U0001f446\U0001f3fb"] = ":point_up_2_tone1:",
["\U0001f446\U0001f3fc"] = ":point_up_2_tone2:",
["\U0001f446\U0001f3fd"] = ":point_up_2_tone3:",
["\U0001f446\U0001f3fe"] = ":point_up_2_tone4:",
["\U0001f446\U0001f3ff"] = ":point_up_2_tone5:",
["\u261d\U0001f3fb"] = ":point_up_tone1:",
["\u261d\U0001f3fc"] = ":point_up_tone2:",
["\u261d\U0001f3fd"] = ":point_up_tone3:",
["\u261d\U0001f3fe"] = ":point_up_tone4:",
["\u261d\U0001f3ff"] = ":point_up_tone5:",
["\U0001f43b\u200d\u2744\ufe0f"] = ":polar_bear:",
["\U0001f693"] = ":police_car:",
["\U0001f46e"] = ":police_officer:",
["\U0001f46e\U0001f3fb"] = ":police_officer_tone1:",
["\U0001f46e\U0001f3fc"] = ":police_officer_tone2:",
["\U0001f46e\U0001f3fd"] = ":police_officer_tone3:",
["\U0001f46e\U0001f3fe"] = ":police_officer_tone4:",
["\U0001f46e\U0001f3ff"] = ":police_officer_tone5:",
["\U0001f429"] = ":poodle:",
["\U0001f4a9"] = ":poop:",
["\U0001f37f"] = ":popcorn:",
["\U0001f3e3"] = ":post_office:",
["\U0001f4ef"] = ":postal_horn:",
["\U0001f4ee"] = ":postbox:",
["\U0001f6b0"] = ":potable_water:",
["\U0001f954"] = ":potato:",
["\U0001fab4"] = ":potted_plant:",
["\U0001f45d"] = ":pouch:",
["\U0001f357"] = ":poultry_leg:",
["\U0001f4b7"] = ":pound:",
["\U0001f63e"] = ":pouting_cat:",
["\U0001f64f"] = ":pray:",
["\U0001f64f\U0001f3fb"] = ":pray_tone1:",
["\U0001f64f\U0001f3fc"] = ":pray_tone2:",
["\U0001f64f\U0001f3fd"] = ":pray_tone3:",
["\U0001f64f\U0001f3fe"] = ":pray_tone4:",
["\U0001f64f\U0001f3ff"] = ":pray_tone5:",
["\U0001f4ff"] = ":prayer_beads:",
["\U0001f930"] = ":pregnant_woman:",
["\U0001f930\U0001f3fb"] = ":pregnant_woman_tone1:",
["\U0001f930\U0001f3fc"] = ":pregnant_woman_tone2:",
["\U0001f930\U0001f3fd"] = ":pregnant_woman_tone3:",
["\U0001f930\U0001f3fe"] = ":pregnant_woman_tone4:",
["\U0001f930\U0001f3ff"] = ":pregnant_woman_tone5:",
["\U0001f968"] = ":pretzel:",
["\U0001f934"] = ":prince:",
["\U0001f934\U0001f3fb"] = ":prince_tone1:",
["\U0001f934\U0001f3fc"] = ":prince_tone2:",
["\U0001f934\U0001f3fd"] = ":prince_tone3:",
["\U0001f934\U0001f3fe"] = ":prince_tone4:",
["\U0001f934\U0001f3ff"] = ":prince_tone5:",
["\U0001f478"] = ":princess:",
["\U0001f478\U0001f3fb"] = ":princess_tone1:",
["\U0001f478\U0001f3fc"] = ":princess_tone2:",
["\U0001f478\U0001f3fd"] = ":princess_tone3:",
["\U0001f478\U0001f3fe"] = ":princess_tone4:",
["\U0001f478\U0001f3ff"] = ":princess_tone5:",
["\U0001f5a8\ufe0f"] = ":printer:",
["\U0001f5a8"] = ":printer:",
["\U0001f9af"] = ":probing_cane:",
["\U0001f4fd\ufe0f"] = ":projector:",
["\U0001f4fd"] = ":projector:",
["\U0001f44a"] = ":punch:",
["\U0001f44a\U0001f3fb"] = ":punch_tone1:",
["\U0001f44a\U0001f3fc"] = ":punch_tone2:",
["\U0001f44a\U0001f3fd"] = ":punch_tone3:",
["\U0001f44a\U0001f3fe"] = ":punch_tone4:",
["\U0001f44a\U0001f3ff"] = ":punch_tone5:",
["\U0001f7e3"] = ":purple_circle:",
["\U0001f49c"] = ":purple_heart:",
["\U0001f7ea"] = ":purple_square:",
["\U0001f45b"] = ":purse:",
["\U0001f4cc"] = ":pushpin:",
["\U0001f6ae"] = ":put_litter_in_its_place:",
["\u2753"] = ":question:",
["\U0001f430"] = ":rabbit:",
["\U0001f407"] = ":rabbit2:",
["\U0001f99d"] = ":raccoon:",
["\U0001f3ce\ufe0f"] = ":race_car:",
["\U0001f3ce"] = ":race_car:",
["\U0001f40e"] = ":racehorse:",
["\U0001f4fb"] = ":radio:",
["\U0001f518"] = ":radio_button:",
["\u2622\ufe0f"] = ":radioactive:",
["\u2622"] = ":radioactive:",
["\U0001f621"] = ":rage:",
["\U0001f683"] = ":railway_car:",
["\U0001f6e4\ufe0f"] = ":railway_track:",
["\U0001f6e4"] = ":railway_track:",
["\U0001f308"] = ":rainbow:",
["\U0001f3f3\ufe0f\u200d\U0001f308"] = ":rainbow_flag:",
["\U0001f91a"] = ":raised_back_of_hand:",
["\U0001f91a\U0001f3fb"] = ":raised_back_of_hand_tone1:",
["\U0001f91a\U0001f3fc"] = ":raised_back_of_hand_tone2:",
["\U0001f91a\U0001f3fd"] = ":raised_back_of_hand_tone3:",
["\U0001f91a\U0001f3fe"] = ":raised_back_of_hand_tone4:",
["\U0001f91a\U0001f3ff"] = ":raised_back_of_hand_tone5:",
["\u270b"] = ":raised_hand:",
["\u270b\U0001f3fb"] = ":raised_hand_tone1:",
["\u270b\U0001f3fc"] = ":raised_hand_tone2:",
["\u270b\U0001f3fd"] = ":raised_hand_tone3:",
["\u270b\U0001f3fe"] = ":raised_hand_tone4:",
["\u270b\U0001f3ff"] = ":raised_hand_tone5:",
["\U0001f64c"] = ":raised_hands:",
["\U0001f64c\U0001f3fb"] = ":raised_hands_tone1:",
["\U0001f64c\U0001f3fc"] = ":raised_hands_tone2:",
["\U0001f64c\U0001f3fd"] = ":raised_hands_tone3:",
["\U0001f64c\U0001f3fe"] = ":raised_hands_tone4:",
["\U0001f64c\U0001f3ff"] = ":raised_hands_tone5:",
["\U0001f40f"] = ":ram:",
["\U0001f35c"] = ":ramen:",
["\U0001f400"] = ":rat:",
["\U0001fa92"] = ":razor:",
["\U0001f9fe"] = ":receipt:",
["\u23fa\ufe0f"] = ":record_button:",
["\u23fa"] = ":record_button:",
["\u267b\ufe0f"] = ":recycle:",
["\u267b"] = ":recycle:",
["\U0001f697"] = ":red_car:",
["\U0001f534"] = ":red_circle:",
["\U0001f9e7"] = ":red_envelope:",
["\U0001f7e5"] = ":red_square:",
["\U0001f1e6"] = ":regional_indicator_a:",
["\U0001f1e7"] = ":regional_indicator_b:",
["\U0001f1e8"] = ":regional_indicator_c:",
["\U0001f1e9"] = ":regional_indicator_d:",
["\U0001f1ea"] = ":regional_indicator_e:",
["\U0001f1eb"] = ":regional_indicator_f:",
["\U0001f1ec"] = ":regional_indicator_g:",
["\U0001f1ed"] = ":regional_indicator_h:",
["\U0001f1ee"] = ":regional_indicator_i:",
["\U0001f1ef"] = ":regional_indicator_j:",
["\U0001f1f0"] = ":regional_indicator_k:",
["\U0001f1f1"] = ":regional_indicator_l:",
["\U0001f1f2"] = ":regional_indicator_m:",
["\U0001f1f3"] = ":regional_indicator_n:",
["\U0001f1f4"] = ":regional_indicator_o:",
["\U0001f1f5"] = ":regional_indicator_p:",
["\U0001f1f6"] = ":regional_indicator_q:",
["\U0001f1f7"] = ":regional_indicator_r:",
["\U0001f1f8"] = ":regional_indicator_s:",
["\U0001f1f9"] = ":regional_indicator_t:",
["\U0001f1fa"] = ":regional_indicator_u:",
["\U0001f1fb"] = ":regional_indicator_v:",
["\U0001f1fc"] = ":regional_indicator_w:",
["\U0001f1fd"] = ":regional_indicator_x:",
["\U0001f1fe"] = ":regional_indicator_y:",
["\U0001f1ff"] = ":regional_indicator_z:",
["\u00ae\ufe0f"] = ":registered:",
["\u00ae"] = ":registered:",
["\u263a\ufe0f"] = ":relaxed:",
["\u263a"] = ":relaxed:",
["\U0001f60c"] = ":relieved:",
["\U0001f397\ufe0f"] = ":reminder_ribbon:",
["\U0001f397"] = ":reminder_ribbon:",
["\U0001f501"] = ":repeat:",
["\U0001f502"] = ":repeat_one:",
["\U0001f6bb"] = ":restroom:",
["\U0001f49e"] = ":revolving_hearts:",
["\u23ea"] = ":rewind:",
["\U0001f98f"] = ":rhino:",
["\U0001f380"] = ":ribbon:",
["\U0001f35a"] = ":rice:",
["\U0001f359"] = ":rice_ball:",
["\U0001f358"] = ":rice_cracker:",
["\U0001f391"] = ":rice_scene:",
["\U0001f91c"] = ":right_facing_fist:",
["\U0001f91c\U0001f3fb"] = ":right_facing_fist_tone1:",
["\U0001f91c\U0001f3fc"] = ":right_facing_fist_tone2:",
["\U0001f91c\U0001f3fd"] = ":right_facing_fist_tone3:",
["\U0001f91c\U0001f3fe"] = ":right_facing_fist_tone4:",
["\U0001f91c\U0001f3ff"] = ":right_facing_fist_tone5:",
["\U0001f48d"] = ":ring:",
["\U0001fa90"] = ":ringed_planet:",
["\U0001f916"] = ":robot:",
["\U0001faa8"] = ":rock:",
["\U0001f680"] = ":rocket:",
["\U0001f923"] = ":rofl:",
["\U0001f9fb"] = ":roll_of_paper:",
["\U0001f3a2"] = ":roller_coaster:",
["\U0001f6fc"] = ":roller_skate:",
["\U0001f644"] = ":rolling_eyes:",
["\U0001f413"] = ":rooster:",
["\U0001f339"] = ":rose:",
["\U0001f3f5\ufe0f"] = ":rosette:",
["\U0001f3f5"] = ":rosette:",
["\U0001f6a8"] = ":rotating_light:",
["\U0001f4cd"] = ":round_pushpin:",
["\U0001f3c9"] = ":rugby_football:",
["\U0001f3bd"] = ":running_shirt_with_sash:",
["\U0001f202\ufe0f"] = ":sa:",
["\U0001f202"] = ":sa:",
["\U0001f9f7"] = ":safety_pin:",
["\U0001f9ba"] = ":safety_vest:",
["\u2650"] = ":sagittarius:",
["\u26f5"] = ":sailboat:",
["\U0001f376"] = ":sake:",
["\U0001f957"] = ":salad:",
["\U0001f9c2"] = ":salt:",
["\U0001f461"] = ":sandal:",
["\U0001f96a"] = ":sandwich:",
["\U0001f385"] = ":santa:",
["\U0001f385\U0001f3fb"] = ":santa_tone1:",
["\U0001f385\U0001f3fc"] = ":santa_tone2:",
["\U0001f385\U0001f3fd"] = ":santa_tone3:",
["\U0001f385\U0001f3fe"] = ":santa_tone4:",
["\U0001f385\U0001f3ff"] = ":santa_tone5:",
["\U0001f97b"] = ":sari:",
["\U0001f4e1"] = ":satellite:",
["\U0001f6f0\ufe0f"] = ":satellite_orbital:",
["\U0001f6f0"] = ":satellite_orbital:",
["\U0001f995"] = ":sauropod:",
["\U0001f3b7"] = ":saxophone:",
["\u2696\ufe0f"] = ":scales:",
["\u2696"] = ":scales:",
["\U0001f9e3"] = ":scarf:",
["\U0001f3eb"] = ":school:",
["\U0001f392"] = ":school_satchel:",
["\U0001f9d1\u200d\U0001f52c"] = ":scientist:",
["\U0001f9d1\U0001f3fb\u200d\U0001f52c"] = ":scientist_tone1:",
["\U0001f9d1\U0001f3fc\u200d\U0001f52c"] = ":scientist_tone2:",
["\U0001f9d1\U0001f3fd\u200d\U0001f52c"] = ":scientist_tone3:",
["\U0001f9d1\U0001f3fe\u200d\U0001f52c"] = ":scientist_tone4:",
["\U0001f9d1\U0001f3ff\u200d\U0001f52c"] = ":scientist_tone5:",
["\u2702\ufe0f"] = ":scissors:",
["\u2702"] = ":scissors:",
["\U0001f6f4"] = ":scooter:",
["\U0001f982"] = ":scorpion:",
["\u264f"] = ":scorpius:",
["\U0001f3f4\U000e0067\U000e0062\U000e0073\U000e0063\U000e0074\U000e007f"] = ":scotland:",
["\U0001f631"] = ":scream:",
["\U0001f640"] = ":scream_cat:",
["\U0001fa9b"] = ":screwdriver:",
["\U0001f4dc"] = ":scroll:",
["\U0001f9ad"] = ":seal:",
["\U0001f4ba"] = ":seat:",
["\U0001f948"] = ":second_place:",
["\u3299\ufe0f"] = ":secret:",
["\u3299"] = ":secret:",
["\U0001f648"] = ":see_no_evil:",
["\U0001f331"] = ":seedling:",
["\U0001f933"] = ":selfie:",
["\U0001f933\U0001f3fb"] = ":selfie_tone1:",
["\U0001f933\U0001f3fc"] = ":selfie_tone2:",
["\U0001f933\U0001f3fd"] = ":selfie_tone3:",
["\U0001f933\U0001f3fe"] = ":selfie_tone4:",
["\U0001f933\U0001f3ff"] = ":selfie_tone5:",
["\U0001f415\u200d\U0001f9ba"] = ":service_dog:",
["\u0037\ufe0f\u20e3"] = ":seven:",
["\u0037\u20e3"] = ":seven:",
["\U0001faa1"] = ":sewing_needle:",
["\U0001f958"] = ":shallow_pan_of_food:",
["\u2618\ufe0f"] = ":shamrock:",
["\u2618"] = ":shamrock:",
["\U0001f988"] = ":shark:",
["\U0001f367"] = ":shaved_ice:",
["\U0001f411"] = ":sheep:",
["\U0001f41a"] = ":shell:",
["\U0001f6e1\ufe0f"] = ":shield:",
["\U0001f6e1"] = ":shield:",
["\u26e9\ufe0f"] = ":shinto_shrine:",
["\u26e9"] = ":shinto_shrine:",
["\U0001f6a2"] = ":ship:",
["\U0001f455"] = ":shirt:",
["\U0001f6cd\ufe0f"] = ":shopping_bags:",
["\U0001f6cd"] = ":shopping_bags:",
["\U0001f6d2"] = ":shopping_cart:",
["\U0001fa73"] = ":shorts:",
["\U0001f6bf"] = ":shower:",
["\U0001f990"] = ":shrimp:",
["\U0001f92b"] = ":shushing_face:",
["\U0001f4f6"] = ":signal_strength:",
["\U0001f9d1\u200d\U0001f3a4"] = ":singer:",
["\U0001f9d1\U0001f3fb\u200d\U0001f3a4"] = ":singer_tone1:",
["\U0001f9d1\U0001f3fc\u200d\U0001f3a4"] = ":singer_tone2:",
["\U0001f9d1\U0001f3fd\u200d\U0001f3a4"] = ":singer_tone3:",
["\U0001f9d1\U0001f3fe\u200d\U0001f3a4"] = ":singer_tone4:",
["\U0001f9d1\U0001f3ff\u200d\U0001f3a4"] = ":singer_tone5:",
["\u0036\ufe0f\u20e3"] = ":six:",
["\u0036\u20e3"] = ":six:",
["\U0001f52f"] = ":six_pointed_star:",
["\U0001f6f9"] = ":skateboard:",
["\U0001f3bf"] = ":ski:",
["\u26f7\ufe0f"] = ":skier:",
["\u26f7"] = ":skier:",
["\U0001f480"] = ":skull:",
["\u2620\ufe0f"] = ":skull_crossbones:",
["\u2620"] = ":skull_crossbones:",
["\U0001f9a8"] = ":skunk:",
["\U0001f6f7"] = ":sled:",
["\U0001f634"] = ":sleeping:",
["\U0001f6cc"] = ":sleeping_accommodation:",
["\U0001f62a"] = ":sleepy:",
["\U0001f641"] = ":slight_frown:",
["\U0001f642"] = ":slight_smile:",
["\U0001f3b0"] = ":slot_machine:",
["\U0001f9a5"] = ":sloth:",
["\U0001f539"] = ":small_blue_diamond:",
["\U0001f538"] = ":small_orange_diamond:",
["\U0001f53a"] = ":small_red_triangle:",
["\U0001f53b"] = ":small_red_triangle_down:",
["\U0001f604"] = ":smile:",
["\U0001f638"] = ":smile_cat:",
["\U0001f603"] = ":smiley:",
["\U0001f63a"] = ":smiley_cat:",
["\U0001f970"] = ":smiling_face_with_3_hearts:",
["\U0001f972"] = ":smiling_face_with_tear:",
["\U0001f608"] = ":smiling_imp:",
["\U0001f60f"] = ":smirk:",
["\U0001f63c"] = ":smirk_cat:",
["\U0001f6ac"] = ":smoking:",
["\U0001f40c"] = ":snail:",
["\U0001f40d"] = ":snake:",
["\U0001f927"] = ":sneezing_face:",
["\U0001f3c2"] = ":snowboarder:",
["\U0001f3c2\U0001f3fb"] = ":snowboarder_tone1:",
["\U0001f3c2\U0001f3fc"] = ":snowboarder_tone2:",
["\U0001f3c2\U0001f3fd"] = ":snowboarder_tone3:",
["\U0001f3c2\U0001f3fe"] = ":snowboarder_tone4:",
["\U0001f3c2\U0001f3ff"] = ":snowboarder_tone5:",
["\u2744\ufe0f"] = ":snowflake:",
["\u2744"] = ":snowflake:",
["\u26c4"] = ":snowman:",
["\u2603\ufe0f"] = ":snowman2:",
["\u2603"] = ":snowman2:",
["\U0001f9fc"] = ":soap:",
["\U0001f62d"] = ":sob:",
["\u26bd"] = ":soccer:",
["\U0001f9e6"] = ":socks:",
["\U0001f94e"] = ":softball:",
["\U0001f51c"] = ":soon:",
["\U0001f198"] = ":sos:",
["\U0001f509"] = ":sound:",
["\U0001f47e"] = ":space_invader:",
["\u2660\ufe0f"] = ":spades:",
["\u2660"] = ":spades:",
["\U0001f35d"] = ":spaghetti:",
["\u2747\ufe0f"] = ":sparkle:",
["\u2747"] = ":sparkle:",
["\U0001f387"] = ":sparkler:",
["\u2728"] = ":sparkles:",
["\U0001f496"] = ":sparkling_heart:",
["\U0001f64a"] = ":speak_no_evil:",
["\U0001f508"] = ":speaker:",
["\U0001f5e3\ufe0f"] = ":speaking_head:",
["\U0001f5e3"] = ":speaking_head:",
["\U0001f4ac"] = ":speech_balloon:",
["\U0001f5e8\ufe0f"] = ":speech_left:",
["\U0001f5e8"] = ":speech_left:",
["\U0001f6a4"] = ":speedboat:",
["\U0001f577\ufe0f"] = ":spider:",
["\U0001f577"] = ":spider:",
["\U0001f578\ufe0f"] = ":spider_web:",
["\U0001f578"] = ":spider_web:",
["\U0001f9fd"] = ":sponge:",
["\U0001f944"] = ":spoon:",
["\U0001f9f4"] = ":squeeze_bottle:",
["\U0001f991"] = ":squid:",
["\U0001f3df\ufe0f"] = ":stadium:",
["\U0001f3df"] = ":stadium:",
["\u2b50"] = ":star:",
["\u262a\ufe0f"] = ":star_and_crescent:",
["\u262a"] = ":star_and_crescent:",
["\u2721\ufe0f"] = ":star_of_david:",
["\u2721"] = ":star_of_david:",
["\U0001f929"] = ":star_struck:",
["\U0001f31f"] = ":star2:",
["\U0001f320"] = ":stars:",
["\U0001f689"] = ":station:",
["\U0001f5fd"] = ":statue_of_liberty:",
["\U0001f682"] = ":steam_locomotive:",
["\U0001fa7a"] = ":stethoscope:",
["\U0001f372"] = ":stew:",
["\u23f9\ufe0f"] = ":stop_button:",
["\u23f9"] = ":stop_button:",
["\u23f1\ufe0f"] = ":stopwatch:",
["\u23f1"] = ":stopwatch:",
["\U0001f4cf"] = ":straight_ruler:",
["\U0001f353"] = ":strawberry:",
["\U0001f61b"] = ":stuck_out_tongue:",
["\U0001f61d"] = ":stuck_out_tongue_closed_eyes:",
["\U0001f61c"] = ":stuck_out_tongue_winking_eye:",
["\U0001f9d1\u200d\U0001f393"] = ":student:",
["\U0001f9d1\U0001f3fb\u200d\U0001f393"] = ":student_tone1:",
["\U0001f9d1\U0001f3fc\u200d\U0001f393"] = ":student_tone2:",
["\U0001f9d1\U0001f3fd\u200d\U0001f393"] = ":student_tone3:",
["\U0001f9d1\U0001f3fe\u200d\U0001f393"] = ":student_tone4:",
["\U0001f9d1\U0001f3ff\u200d\U0001f393"] = ":student_tone5:",
["\U0001f959"] = ":stuffed_flatbread:",
["\U0001f31e"] = ":sun_with_face:",
["\U0001f33b"] = ":sunflower:",
["\U0001f60e"] = ":sunglasses:",
["\u2600\ufe0f"] = ":sunny:",
["\u2600"] = ":sunny:",
["\U0001f305"] = ":sunrise:",
["\U0001f304"] = ":sunrise_over_mountains:",
["\U0001f9b8"] = ":superhero:",
["\U0001f9b8\U0001f3fb"] = ":superhero_tone1:",
["\U0001f9b8\U0001f3fc"] = ":superhero_tone2:",
["\U0001f9b8\U0001f3fd"] = ":superhero_tone3:",
["\U0001f9b8\U0001f3fe"] = ":superhero_tone4:",
["\U0001f9b8\U0001f3ff"] = ":superhero_tone5:",
["\U0001f9b9"] = ":supervillain:",
["\U0001f9b9\U0001f3fb"] = ":supervillain_tone1:",
["\U0001f9b9\U0001f3fc"] = ":supervillain_tone2:",
["\U0001f9b9\U0001f3fd"] = ":supervillain_tone3:",
["\U0001f9b9\U0001f3fe"] = ":supervillain_tone4:",
["\U0001f9b9\U0001f3ff"] = ":supervillain_tone5:",
["\U0001f363"] = ":sushi:",
["\U0001f69f"] = ":suspension_railway:",
["\U0001f9a2"] = ":swan:",
["\U0001f613"] = ":sweat:",
["\U0001f4a6"] = ":sweat_drops:",
["\U0001f605"] = ":sweat_smile:",
["\U0001f360"] = ":sweet_potato:",
["\U0001f523"] = ":symbols:",
["\U0001f54d"] = ":synagogue:",
["\U0001f489"] = ":syringe:",
["\U0001f996"] = ":t_rex:",
["\U0001f32e"] = ":taco:",
["\U0001f389"] = ":tada:",
["\U0001f961"] = ":takeout_box:",
["\U0001fad4"] = ":tamale:",
["\U0001f38b"] = ":tanabata_tree:",
["\U0001f34a"] = ":tangerine:",
["\u2649"] = ":taurus:",
["\U0001f695"] = ":taxi:",
["\U0001f375"] = ":tea:",
["\U0001f9d1\u200d\U0001f3eb"] = ":teacher:",
["\U0001f9d1\U0001f3fb\u200d\U0001f3eb"] = ":teacher_tone1:",
["\U0001f9d1\U0001f3fc\u200d\U0001f3eb"] = ":teacher_tone2:",
["\U0001f9d1\U0001f3fd\u200d\U0001f3eb"] = ":teacher_tone3:",
["\U0001f9d1\U0001f3fe\u200d\U0001f3eb"] = ":teacher_tone4:",
["\U0001f9d1\U0001f3ff\u200d\U0001f3eb"] = ":teacher_tone5:",
["\U0001fad6"] = ":teapot:",
["\U0001f9d1\u200d\U0001f4bb"] = ":technologist:",
["\U0001f9d1\U0001f3fb\u200d\U0001f4bb"] = ":technologist_tone1:",
["\U0001f9d1\U0001f3fc\u200d\U0001f4bb"] = ":technologist_tone2:",
["\U0001f9d1\U0001f3fd\u200d\U0001f4bb"] = ":technologist_tone3:",
["\U0001f9d1\U0001f3fe\u200d\U0001f4bb"] = ":technologist_tone4:",
["\U0001f9d1\U0001f3ff\u200d\U0001f4bb"] = ":technologist_tone5:",
["\U0001f9f8"] = ":teddy_bear:",
["\u260e\ufe0f"] = ":telephone:",
["\u260e"] = ":telephone:",
["\U0001f4de"] = ":telephone_receiver:",
["\U0001f52d"] = ":telescope:",
["\U0001f3be"] = ":tennis:",
["\u26fa"] = ":tent:",
["\U0001f9ea"] = ":test_tube:",
["\U0001f321\ufe0f"] = ":thermometer:",
["\U0001f321"] = ":thermometer:",
["\U0001f912"] = ":thermometer_face:",
["\U0001f914"] = ":thinking:",
["\U0001f949"] = ":third_place:",
["\U0001fa74"] = ":thong_sandal:",
["\U0001f4ad"] = ":thought_balloon:",
["\U0001f9f5"] = ":thread:",
["\u0033\ufe0f\u20e3"] = ":three:",
["\u0033\u20e3"] = ":three:",
["\U0001f44e"] = ":thumbsdown:",
["\U0001f44e\U0001f3fb"] = ":thumbsdown_tone1:",
["\U0001f44e\U0001f3fc"] = ":thumbsdown_tone2:",
["\U0001f44e\U0001f3fd"] = ":thumbsdown_tone3:",
["\U0001f44e\U0001f3fe"] = ":thumbsdown_tone4:",
["\U0001f44e\U0001f3ff"] = ":thumbsdown_tone5:",
["\U0001f44d"] = ":thumbsup:",
["\U0001f44d\U0001f3fb"] = ":thumbsup_tone1:",
["\U0001f44d\U0001f3fc"] = ":thumbsup_tone2:",
["\U0001f44d\U0001f3fd"] = ":thumbsup_tone3:",
["\U0001f44d\U0001f3fe"] = ":thumbsup_tone4:",
["\U0001f44d\U0001f3ff"] = ":thumbsup_tone5:",
["\u26c8\ufe0f"] = ":thunder_cloud_rain:",
["\u26c8"] = ":thunder_cloud_rain:",
["\U0001f3ab"] = ":ticket:",
["\U0001f39f\ufe0f"] = ":tickets:",
["\U0001f39f"] = ":tickets:",
["\U0001f42f"] = ":tiger:",
["\U0001f405"] = ":tiger2:",
["\u23f2\ufe0f"] = ":timer:",
["\u23f2"] = ":timer:",
["\U0001f62b"] = ":tired_face:",
["\u2122\ufe0f"] = ":tm:",
["\u2122"] = ":tm:",
["\U0001f6bd"] = ":toilet:",
["\U0001f5fc"] = ":tokyo_tower:",
["\U0001f345"] = ":tomato:",
["\U0001f445"] = ":tongue:",
["\U0001f9f0"] = ":toolbox:",
["\U0001f6e0\ufe0f"] = ":tools:",
["\U0001f6e0"] = ":tools:",
["\U0001f9b7"] = ":tooth:",
["\U0001faa5"] = ":toothbrush:",
["\U0001f51d"] = ":top:",
["\U0001f3a9"] = ":tophat:",
["\u23ed\ufe0f"] = ":track_next:",
["\u23ed"] = ":track_next:",
["\u23ee\ufe0f"] = ":track_previous:",
["\u23ee"] = ":track_previous:",
["\U0001f5b2\ufe0f"] = ":trackball:",
["\U0001f5b2"] = ":trackball:",
["\U0001f69c"] = ":tractor:",
["\U0001f6a5"] = ":traffic_light:",
["\U0001f68b"] = ":train:",
["\U0001f686"] = ":train2:",
["\U0001f68a"] = ":tram:",
["\U0001f3f3\ufe0f\u200d\u26a7\ufe0f"] = ":transgender_flag:",
["\u26a7"] = ":transgender_symbol:",
["\U0001f6a9"] = ":triangular_flag_on_post:",
["\U0001f4d0"] = ":triangular_ruler:",
["\U0001f531"] = ":trident:",
["\U0001f624"] = ":triumph:",
["\U0001f68e"] = ":trolleybus:",
["\U0001f3c6"] = ":trophy:",
["\U0001f379"] = ":tropical_drink:",
["\U0001f420"] = ":tropical_fish:",
["\U0001f69a"] = ":truck:",
["\U0001f3ba"] = ":trumpet:",
["\U0001f337"] = ":tulip:",
["\U0001f943"] = ":tumbler_glass:",
["\U0001f983"] = ":turkey:",
["\U0001f422"] = ":turtle:",
["\U0001f4fa"] = ":tv:",
["\U0001f500"] = ":twisted_rightwards_arrows:",
["\u0032\ufe0f\u20e3"] = ":two:",
["\u0032\u20e3"] = ":two:",
["\U0001f495"] = ":two_hearts:",
["\U0001f46c"] = ":two_men_holding_hands:",
["\U0001f239"] = ":u5272:",
["\U0001f234"] = ":u5408:",
["\U0001f23a"] = ":u55b6:",
["\U0001f22f"] = ":u6307:",
["\U0001f237\ufe0f"] = ":u6708:",
["\U0001f237"] = ":u6708:",
["\U0001f236"] = ":u6709:",
["\U0001f235"] = ":u6e80:",
["\U0001f21a"] = ":u7121:",
["\U0001f238"] = ":u7533:",
["\U0001f232"] = ":u7981:",
["\U0001f233"] = ":u7a7a:",
["\u2614"] = ":umbrella:",
["\u2602\ufe0f"] = ":umbrella2:",
["\u2602"] = ":umbrella2:",
["\U0001f612"] = ":unamused:",
["\U0001f51e"] = ":underage:",
["\U0001f984"] = ":unicorn:",
["\U0001f1fa\U0001f1f3"] = ":united_nations:",
["\U0001f513"] = ":unlock:",
["\U0001f199"] = ":up:",
["\U0001f643"] = ":upside_down:",
["\u26b1\ufe0f"] = ":urn:",
["\u26b1"] = ":urn:",
["\u270c\ufe0f"] = ":v:",
["\u270c"] = ":v:",
["\u270c\U0001f3fb"] = ":v_tone1:",
["\u270c\U0001f3fc"] = ":v_tone2:",
["\u270c\U0001f3fd"] = ":v_tone3:",
["\u270c\U0001f3fe"] = ":v_tone4:",
["\u270c\U0001f3ff"] = ":v_tone5:",
["\U0001f9db"] = ":vampire:",
["\U0001f9db\U0001f3fb"] = ":vampire_tone1:",
["\U0001f9db\U0001f3fc"] = ":vampire_tone2:",
["\U0001f9db\U0001f3fd"] = ":vampire_tone3:",
["\U0001f9db\U0001f3fe"] = ":vampire_tone4:",
["\U0001f9db\U0001f3ff"] = ":vampire_tone5:",
["\U0001f6a6"] = ":vertical_traffic_light:",
["\U0001f4fc"] = ":vhs:",
["\U0001f4f3"] = ":vibration_mode:",
["\U0001f4f9"] = ":video_camera:",
["\U0001f3ae"] = ":video_game:",
["\U0001f3bb"] = ":violin:",
["\u264d"] = ":virgo:",
["\U0001f30b"] = ":volcano:",
["\U0001f3d0"] = ":volleyball:",
["\U0001f19a"] = ":vs:",
["\U0001f596"] = ":vulcan:",
["\U0001f596\U0001f3fb"] = ":vulcan_tone1:",
["\U0001f596\U0001f3fc"] = ":vulcan_tone2:",
["\U0001f596\U0001f3fd"] = ":vulcan_tone3:",
["\U0001f596\U0001f3fe"] = ":vulcan_tone4:",
["\U0001f596\U0001f3ff"] = ":vulcan_tone5:",
["\U0001f9c7"] = ":waffle:",
["\U0001f3f4\U000e0067\U000e0062\U000e0077\U000e006c\U000e0073\U000e007f"] = ":wales:",
["\U0001f318"] = ":waning_crescent_moon:",
["\U0001f316"] = ":waning_gibbous_moon:",
["\u26a0\ufe0f"] = ":warning:",
["\u26a0"] = ":warning:",
["\U0001f5d1\ufe0f"] = ":wastebasket:",
["\U0001f5d1"] = ":wastebasket:",
["\u231a"] = ":watch:",
["\U0001f403"] = ":water_buffalo:",
["\U0001f349"] = ":watermelon:",
["\U0001f44b"] = ":wave:",
["\U0001f44b\U0001f3fb"] = ":wave_tone1:",
["\U0001f44b\U0001f3fc"] = ":wave_tone2:",
["\U0001f44b\U0001f3fd"] = ":wave_tone3:",
["\U0001f44b\U0001f3fe"] = ":wave_tone4:",
["\U0001f44b\U0001f3ff"] = ":wave_tone5:",
["\u3030\ufe0f"] = ":wavy_dash:",
["\u3030"] = ":wavy_dash:",
["\U0001f312"] = ":waxing_crescent_moon:",
["\U0001f314"] = ":waxing_gibbous_moon:",
["\U0001f6be"] = ":wc:",
["\U0001f629"] = ":weary:",
["\U0001f492"] = ":wedding:",
["\U0001f433"] = ":whale:",
["\U0001f40b"] = ":whale2:",
["\u2638\ufe0f"] = ":wheel_of_dharma:",
["\u2638"] = ":wheel_of_dharma:",
["\u267f"] = ":wheelchair:",
["\u2705"] = ":white_check_mark:",
["\u26aa"] = ":white_circle:",
["\U0001f4ae"] = ":white_flower:",
["\U0001f90d"] = ":white_heart:",
["\u2b1c"] = ":white_large_square:",
["\u25fd"] = ":white_medium_small_square:",
["\u25fb\ufe0f"] = ":white_medium_square:",
["\u25fb"] = ":white_medium_square:",
["\u25ab\ufe0f"] = ":white_small_square:",
["\u25ab"] = ":white_small_square:",
["\U0001f533"] = ":white_square_button:",
["\U0001f325\ufe0f"] = ":white_sun_cloud:",
["\U0001f325"] = ":white_sun_cloud:",
["\U0001f326\ufe0f"] = ":white_sun_rain_cloud:",
["\U0001f326"] = ":white_sun_rain_cloud:",
["\U0001f324\ufe0f"] = ":white_sun_small_cloud:",
["\U0001f324"] = ":white_sun_small_cloud:",
["\U0001f940"] = ":wilted_rose:",
["\U0001f32c\ufe0f"] = ":wind_blowing_face:",
["\U0001f32c"] = ":wind_blowing_face:",
["\U0001f390"] = ":wind_chime:",
["\U0001fa9f"] = ":window:",
["\U0001f377"] = ":wine_glass:",
["\U0001f609"] = ":wink:",
["\U0001f43a"] = ":wolf:",
["\U0001f469"] = ":woman:",
["\U0001f46b"] = ":woman_and_man_holding_hands_tone5_tone4:",
["\U0001f469\u200d\U0001f3a8"] = ":woman_artist:",
["\U0001f469\U0001f3fb\u200d\U0001f3a8"] = ":woman_artist_tone1:",
["\U0001f469\U0001f3fc\u200d\U0001f3a8"] = ":woman_artist_tone2:",
["\U0001f469\U0001f3fd\u200d\U0001f3a8"] = ":woman_artist_tone3:",
["\U0001f469\U0001f3fe\u200d\U0001f3a8"] = ":woman_artist_tone4:",
["\U0001f469\U0001f3ff\u200d\U0001f3a8"] = ":woman_artist_tone5:",
["\U0001f469\u200d\U0001f680"] = ":woman_astronaut:",
["\U0001f469\U0001f3fb\u200d\U0001f680"] = ":woman_astronaut_tone1:",
["\U0001f469\U0001f3fc\u200d\U0001f680"] = ":woman_astronaut_tone2:",
["\U0001f469\U0001f3fd\u200d\U0001f680"] = ":woman_astronaut_tone3:",
["\U0001f469\U0001f3fe\u200d\U0001f680"] = ":woman_astronaut_tone4:",
["\U0001f469\U0001f3ff\u200d\U0001f680"] = ":woman_astronaut_tone5:",
["\U0001f469\u200d\U0001f9b2"] = ":woman_bald:",
["\U0001f469\U0001f3fb\u200d\U0001f9b2"] = ":woman_bald_tone1:",
["\U0001f469\U0001f3fc\u200d\U0001f9b2"] = ":woman_bald_tone2:",
["\U0001f469\U0001f3fd\u200d\U0001f9b2"] = ":woman_bald_tone3:",
["\U0001f469\U0001f3fe\u200d\U0001f9b2"] = ":woman_bald_tone4:",
["\U0001f469\U0001f3ff\u200d\U0001f9b2"] = ":woman_bald_tone5:",
["\U0001f6b4\u200d\u2640\ufe0f"] = ":woman_biking:",
["\U0001f6b4\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_biking_tone1:",
["\U0001f6b4\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_biking_tone2:",
["\U0001f6b4\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_biking_tone3:",
["\U0001f6b4\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_biking_tone4:",
["\U0001f6b4\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_biking_tone5:",
["\u26f9\ufe0f\u200d\u2640\ufe0f"] = ":woman_bouncing_ball:",
["\u26f9\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_bouncing_ball_tone1:",
["\u26f9\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_bouncing_ball_tone2:",
["\u26f9\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_bouncing_ball_tone3:",
["\u26f9\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_bouncing_ball_tone4:",
["\u26f9\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_bouncing_ball_tone5:",
["\U0001f647\u200d\u2640\ufe0f"] = ":woman_bowing:",
["\U0001f647\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_bowing_tone1:",
["\U0001f647\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_bowing_tone2:",
["\U0001f647\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_bowing_tone3:",
["\U0001f647\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_bowing_tone4:",
["\U0001f647\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_bowing_tone5:",
["\U0001f938\u200d\u2640\ufe0f"] = ":woman_cartwheeling:",
["\U0001f938\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_cartwheeling_tone1:",
["\U0001f938\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_cartwheeling_tone2:",
["\U0001f938\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_cartwheeling_tone3:",
["\U0001f938\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_cartwheeling_tone4:",
["\U0001f938\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_cartwheeling_tone5:",
["\U0001f9d7\u200d\u2640\ufe0f"] = ":woman_climbing:",
["\U0001f9d7\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_climbing_tone1:",
["\U0001f9d7\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_climbing_tone2:",
["\U0001f9d7\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_climbing_tone3:",
["\U0001f9d7\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_climbing_tone4:",
["\U0001f9d7\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_climbing_tone5:",
["\U0001f477\u200d\u2640\ufe0f"] = ":woman_construction_worker:",
["\U0001f477\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_construction_worker_tone1:",
["\U0001f477\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_construction_worker_tone2:",
["\U0001f477\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_construction_worker_tone3:",
["\U0001f477\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_construction_worker_tone4:",
["\U0001f477\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_construction_worker_tone5:",
["\U0001f469\u200d\U0001f373"] = ":woman_cook:",
["\U0001f469\U0001f3fb\u200d\U0001f373"] = ":woman_cook_tone1:",
["\U0001f469\U0001f3fc\u200d\U0001f373"] = ":woman_cook_tone2:",
["\U0001f469\U0001f3fd\u200d\U0001f373"] = ":woman_cook_tone3:",
["\U0001f469\U0001f3fe\u200d\U0001f373"] = ":woman_cook_tone4:",
["\U0001f469\U0001f3ff\u200d\U0001f373"] = ":woman_cook_tone5:",
["\U0001f469\u200d\U0001f9b1"] = ":woman_curly_haired:",
["\U0001f469\U0001f3fb\u200d\U0001f9b1"] = ":woman_curly_haired_tone1:",
["\U0001f469\U0001f3fc\u200d\U0001f9b1"] = ":woman_curly_haired_tone2:",
["\U0001f469\U0001f3fd\u200d\U0001f9b1"] = ":woman_curly_haired_tone3:",
["\U0001f469\U0001f3fe\u200d\U0001f9b1"] = ":woman_curly_haired_tone4:",
["\U0001f469\U0001f3ff\u200d\U0001f9b1"] = ":woman_curly_haired_tone5:",
["\U0001f575\ufe0f\u200d\u2640\ufe0f"] = ":woman_detective:",
["\U0001f575\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_detective_tone1:",
["\U0001f575\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_detective_tone2:",
["\U0001f575\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_detective_tone3:",
["\U0001f575\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_detective_tone4:",
["\U0001f575\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_detective_tone5:",
["\U0001f9dd\u200d\u2640\ufe0f"] = ":woman_elf:",
["\U0001f9dd\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_elf_tone1:",
["\U0001f9dd\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_elf_tone2:",
["\U0001f9dd\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_elf_tone3:",
["\U0001f9dd\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_elf_tone4:",
["\U0001f9dd\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_elf_tone5:",
["\U0001f926\u200d\u2640\ufe0f"] = ":woman_facepalming:",
["\U0001f926\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_facepalming_tone1:",
["\U0001f926\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_facepalming_tone2:",
["\U0001f926\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_facepalming_tone3:",
["\U0001f926\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_facepalming_tone4:",
["\U0001f926\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_facepalming_tone5:",
["\U0001f469\u200d\U0001f3ed"] = ":woman_factory_worker:",
["\U0001f469\U0001f3fb\u200d\U0001f3ed"] = ":woman_factory_worker_tone1:",
["\U0001f469\U0001f3fc\u200d\U0001f3ed"] = ":woman_factory_worker_tone2:",
["\U0001f469\U0001f3fd\u200d\U0001f3ed"] = ":woman_factory_worker_tone3:",
["\U0001f469\U0001f3fe\u200d\U0001f3ed"] = ":woman_factory_worker_tone4:",
["\U0001f469\U0001f3ff\u200d\U0001f3ed"] = ":woman_factory_worker_tone5:",
["\U0001f9da\u200d\u2640\ufe0f"] = ":woman_fairy:",
["\U0001f9da\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_fairy_tone1:",
["\U0001f9da\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_fairy_tone2:",
["\U0001f9da\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_fairy_tone3:",
["\U0001f9da\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_fairy_tone4:",
["\U0001f9da\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_fairy_tone5:",
["\U0001f469\u200d\U0001f33e"] = ":woman_farmer:",
["\U0001f469\U0001f3fb\u200d\U0001f33e"] = ":woman_farmer_tone1:",
["\U0001f469\U0001f3fc\u200d\U0001f33e"] = ":woman_farmer_tone2:",
["\U0001f469\U0001f3fd\u200d\U0001f33e"] = ":woman_farmer_tone3:",
["\U0001f469\U0001f3fe\u200d\U0001f33e"] = ":woman_farmer_tone4:",
["\U0001f469\U0001f3ff\u200d\U0001f33e"] = ":woman_farmer_tone5:",
["\U0001f469\u200d\U0001f37c"] = ":woman_feeding_baby:",
["\U0001f469\U0001f3fb\u200d\U0001f37c"] = ":woman_feeding_baby_tone1:",
["\U0001f469\U0001f3fc\u200d\U0001f37c"] = ":woman_feeding_baby_tone2:",
["\U0001f469\U0001f3fd\u200d\U0001f37c"] = ":woman_feeding_baby_tone3:",
["\U0001f469\U0001f3fe\u200d\U0001f37c"] = ":woman_feeding_baby_tone4:",
["\U0001f469\U0001f3ff\u200d\U0001f37c"] = ":woman_feeding_baby_tone5:",
["\U0001f469\u200d\U0001f692"] = ":woman_firefighter:",
["\U0001f469\U0001f3fb\u200d\U0001f692"] = ":woman_firefighter_tone1:",
["\U0001f469\U0001f3fc\u200d\U0001f692"] = ":woman_firefighter_tone2:",
["\U0001f469\U0001f3fd\u200d\U0001f692"] = ":woman_firefighter_tone3:",
["\U0001f469\U0001f3fe\u200d\U0001f692"] = ":woman_firefighter_tone4:",
["\U0001f469\U0001f3ff\u200d\U0001f692"] = ":woman_firefighter_tone5:",
["\U0001f64d\u200d\u2640\ufe0f"] = ":woman_frowning:",
["\U0001f64d\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_frowning_tone1:",
["\U0001f64d\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_frowning_tone2:",
["\U0001f64d\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_frowning_tone3:",
["\U0001f64d\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_frowning_tone4:",
["\U0001f64d\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_frowning_tone5:",
["\U0001f9de\u200d\u2640\ufe0f"] = ":woman_genie:",
["\U0001f645\u200d\u2640\ufe0f"] = ":woman_gesturing_no:",
["\U0001f645\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_gesturing_no_tone1:",
["\U0001f645\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_gesturing_no_tone2:",
["\U0001f645\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_gesturing_no_tone3:",
["\U0001f645\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_gesturing_no_tone4:",
["\U0001f645\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_gesturing_no_tone5:",
["\U0001f646\u200d\u2640\ufe0f"] = ":woman_gesturing_ok:",
["\U0001f646\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_gesturing_ok_tone1:",
["\U0001f646\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_gesturing_ok_tone2:",
["\U0001f646\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_gesturing_ok_tone3:",
["\U0001f646\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_gesturing_ok_tone4:",
["\U0001f646\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_gesturing_ok_tone5:",
["\U0001f486\u200d\u2640\ufe0f"] = ":woman_getting_face_massage:",
["\U0001f486\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_getting_face_massage_tone1:",
["\U0001f486\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_getting_face_massage_tone2:",
["\U0001f486\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_getting_face_massage_tone3:",
["\U0001f486\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_getting_face_massage_tone4:",
["\U0001f486\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_getting_face_massage_tone5:",
["\U0001f487\u200d\u2640\ufe0f"] = ":woman_getting_haircut:",
["\U0001f487\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_getting_haircut_tone1:",
["\U0001f487\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_getting_haircut_tone2:",
["\U0001f487\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_getting_haircut_tone3:",
["\U0001f487\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_getting_haircut_tone4:",
["\U0001f487\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_getting_haircut_tone5:",
["\U0001f3cc\ufe0f\u200d\u2640\ufe0f"] = ":woman_golfing:",
["\U0001f3cc\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_golfing_tone1:",
["\U0001f3cc\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_golfing_tone2:",
["\U0001f3cc\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_golfing_tone3:",
["\U0001f3cc\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_golfing_tone4:",
["\U0001f3cc\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_golfing_tone5:",
["\U0001f482\u200d\u2640\ufe0f"] = ":woman_guard:",
["\U0001f482\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_guard_tone1:",
["\U0001f482\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_guard_tone2:",
["\U0001f482\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_guard_tone3:",
["\U0001f482\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_guard_tone4:",
["\U0001f482\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_guard_tone5:",
["\U0001f469\u200d\u2695\ufe0f"] = ":woman_health_worker:",
["\U0001f469\U0001f3fb\u200d\u2695\ufe0f"] = ":woman_health_worker_tone1:",
["\U0001f469\U0001f3fc\u200d\u2695\ufe0f"] = ":woman_health_worker_tone2:",
["\U0001f469\U0001f3fd\u200d\u2695\ufe0f"] = ":woman_health_worker_tone3:",
["\U0001f469\U0001f3fe\u200d\u2695\ufe0f"] = ":woman_health_worker_tone4:",
["\U0001f469\U0001f3ff\u200d\u2695\ufe0f"] = ":woman_health_worker_tone5:",
["\U0001f9d8\u200d\u2640\ufe0f"] = ":woman_in_lotus_position:",
["\U0001f9d8\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_in_lotus_position_tone1:",
["\U0001f9d8\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_in_lotus_position_tone2:",
["\U0001f9d8\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_in_lotus_position_tone3:",
["\U0001f9d8\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_in_lotus_position_tone4:",
["\U0001f9d8\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_in_lotus_position_tone5:",
["\U0001f469\u200d\U0001f9bd"] = ":woman_in_manual_wheelchair:",
["\U0001f469\U0001f3fb\u200d\U0001f9bd"] = ":woman_in_manual_wheelchair_tone1:",
["\U0001f469\U0001f3fc\u200d\U0001f9bd"] = ":woman_in_manual_wheelchair_tone2:",
["\U0001f469\U0001f3fd\u200d\U0001f9bd"] = ":woman_in_manual_wheelchair_tone3:",
["\U0001f469\U0001f3fe\u200d\U0001f9bd"] = ":woman_in_manual_wheelchair_tone4:",
["\U0001f469\U0001f3ff\u200d\U0001f9bd"] = ":woman_in_manual_wheelchair_tone5:",
["\U0001f469\u200d\U0001f9bc"] = ":woman_in_motorized_wheelchair:",
["\U0001f469\U0001f3fb\u200d\U0001f9bc"] = ":woman_in_motorized_wheelchair_tone1:",
["\U0001f469\U0001f3fc\u200d\U0001f9bc"] = ":woman_in_motorized_wheelchair_tone2:",
["\U0001f469\U0001f3fd\u200d\U0001f9bc"] = ":woman_in_motorized_wheelchair_tone3:",
["\U0001f469\U0001f3fe\u200d\U0001f9bc"] = ":woman_in_motorized_wheelchair_tone4:",
["\U0001f469\U0001f3ff\u200d\U0001f9bc"] = ":woman_in_motorized_wheelchair_tone5:",
["\U0001f9d6\u200d\u2640\ufe0f"] = ":woman_in_steamy_room:",
["\U0001f9d6\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_in_steamy_room_tone1:",
["\U0001f9d6\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_in_steamy_room_tone2:",
["\U0001f9d6\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_in_steamy_room_tone3:",
["\U0001f9d6\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_in_steamy_room_tone4:",
["\U0001f9d6\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_in_steamy_room_tone5:",
["\U0001f935\u200d\u2640\ufe0f"] = ":woman_in_tuxedo:",
["\U0001f935\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_in_tuxedo_tone1:",
["\U0001f935\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_in_tuxedo_tone2:",
["\U0001f935\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_in_tuxedo_tone3:",
["\U0001f935\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_in_tuxedo_tone4:",
["\U0001f935\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_in_tuxedo_tone5:",
["\U0001f469\u200d\u2696\ufe0f"] = ":woman_judge:",
["\U0001f469\U0001f3fb\u200d\u2696\ufe0f"] = ":woman_judge_tone1:",
["\U0001f469\U0001f3fc\u200d\u2696\ufe0f"] = ":woman_judge_tone2:",
["\U0001f469\U0001f3fd\u200d\u2696\ufe0f"] = ":woman_judge_tone3:",
["\U0001f469\U0001f3fe\u200d\u2696\ufe0f"] = ":woman_judge_tone4:",
["\U0001f469\U0001f3ff\u200d\u2696\ufe0f"] = ":woman_judge_tone5:",
["\U0001f939\u200d\u2640\ufe0f"] = ":woman_juggling:",
["\U0001f939\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_juggling_tone1:",
["\U0001f939\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_juggling_tone2:",
["\U0001f939\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_juggling_tone3:",
["\U0001f939\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_juggling_tone4:",
["\U0001f939\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_juggling_tone5:",
["\U0001f9ce\u200d\u2640\ufe0f"] = ":woman_kneeling:",
["\U0001f9ce\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_kneeling_tone1:",
["\U0001f9ce\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_kneeling_tone2:",
["\U0001f9ce\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_kneeling_tone3:",
["\U0001f9ce\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_kneeling_tone4:",
["\U0001f9ce\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_kneeling_tone5:",
["\U0001f3cb\ufe0f\u200d\u2640\ufe0f"] = ":woman_lifting_weights:",
["\U0001f3cb\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_lifting_weights_tone1:",
["\U0001f3cb\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_lifting_weights_tone2:",
["\U0001f3cb\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_lifting_weights_tone3:",
["\U0001f3cb\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_lifting_weights_tone4:",
["\U0001f3cb\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_lifting_weights_tone5:",
["\U0001f9d9\u200d\u2640\ufe0f"] = ":woman_mage:",
["\U0001f9d9\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_mage_tone1:",
["\U0001f9d9\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_mage_tone2:",
["\U0001f9d9\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_mage_tone3:",
["\U0001f9d9\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_mage_tone4:",
["\U0001f9d9\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_mage_tone5:",
["\U0001f469\u200d\U0001f527"] = ":woman_mechanic:",
["\U0001f469\U0001f3fb\u200d\U0001f527"] = ":woman_mechanic_tone1:",
["\U0001f469\U0001f3fc\u200d\U0001f527"] = ":woman_mechanic_tone2:",
["\U0001f469\U0001f3fd\u200d\U0001f527"] = ":woman_mechanic_tone3:",
["\U0001f469\U0001f3fe\u200d\U0001f527"] = ":woman_mechanic_tone4:",
["\U0001f469\U0001f3ff\u200d\U0001f527"] = ":woman_mechanic_tone5:",
["\U0001f6b5\u200d\u2640\ufe0f"] = ":woman_mountain_biking:",
["\U0001f6b5\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_mountain_biking_tone1:",
["\U0001f6b5\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_mountain_biking_tone2:",
["\U0001f6b5\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_mountain_biking_tone3:",
["\U0001f6b5\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_mountain_biking_tone4:",
["\U0001f6b5\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_mountain_biking_tone5:",
["\U0001f469\u200d\U0001f4bc"] = ":woman_office_worker:",
["\U0001f469\U0001f3fb\u200d\U0001f4bc"] = ":woman_office_worker_tone1:",
["\U0001f469\U0001f3fc\u200d\U0001f4bc"] = ":woman_office_worker_tone2:",
["\U0001f469\U0001f3fd\u200d\U0001f4bc"] = ":woman_office_worker_tone3:",
["\U0001f469\U0001f3fe\u200d\U0001f4bc"] = ":woman_office_worker_tone4:",
["\U0001f469\U0001f3ff\u200d\U0001f4bc"] = ":woman_office_worker_tone5:",
["\U0001f469\u200d\u2708\ufe0f"] = ":woman_pilot:",
["\U0001f469\U0001f3fb\u200d\u2708\ufe0f"] = ":woman_pilot_tone1:",
["\U0001f469\U0001f3fc\u200d\u2708\ufe0f"] = ":woman_pilot_tone2:",
["\U0001f469\U0001f3fd\u200d\u2708\ufe0f"] = ":woman_pilot_tone3:",
["\U0001f469\U0001f3fe\u200d\u2708\ufe0f"] = ":woman_pilot_tone4:",
["\U0001f469\U0001f3ff\u200d\u2708\ufe0f"] = ":woman_pilot_tone5:",
["\U0001f93e\u200d\u2640\ufe0f"] = ":woman_playing_handball:",
["\U0001f93e\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_playing_handball_tone1:",
["\U0001f93e\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_playing_handball_tone2:",
["\U0001f93e\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_playing_handball_tone3:",
["\U0001f93e\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_playing_handball_tone4:",
["\U0001f93e\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_playing_handball_tone5:",
["\U0001f93d\u200d\u2640\ufe0f"] = ":woman_playing_water_polo:",
["\U0001f93d\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_playing_water_polo_tone1:",
["\U0001f93d\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_playing_water_polo_tone2:",
["\U0001f93d\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_playing_water_polo_tone3:",
["\U0001f93d\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_playing_water_polo_tone4:",
["\U0001f93d\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_playing_water_polo_tone5:",
["\U0001f46e\u200d\u2640\ufe0f"] = ":woman_police_officer:",
["\U0001f46e\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_police_officer_tone1:",
["\U0001f46e\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_police_officer_tone2:",
["\U0001f46e\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_police_officer_tone3:",
["\U0001f46e\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_police_officer_tone4:",
["\U0001f46e\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_police_officer_tone5:",
["\U0001f64e\u200d\u2640\ufe0f"] = ":woman_pouting:",
["\U0001f64e\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_pouting_tone1:",
["\U0001f64e\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_pouting_tone2:",
["\U0001f64e\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_pouting_tone3:",
["\U0001f64e\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_pouting_tone4:",
["\U0001f64e\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_pouting_tone5:",
["\U0001f64b\u200d\u2640\ufe0f"] = ":woman_raising_hand:",
["\U0001f64b\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_raising_hand_tone1:",
["\U0001f64b\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_raising_hand_tone2:",
["\U0001f64b\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_raising_hand_tone3:",
["\U0001f64b\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_raising_hand_tone4:",
["\U0001f64b\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_raising_hand_tone5:",
["\U0001f469\u200d\U0001f9b0"] = ":woman_red_haired:",
["\U0001f469\U0001f3fb\u200d\U0001f9b0"] = ":woman_red_haired_tone1:",
["\U0001f469\U0001f3fc\u200d\U0001f9b0"] = ":woman_red_haired_tone2:",
["\U0001f469\U0001f3fd\u200d\U0001f9b0"] = ":woman_red_haired_tone3:",
["\U0001f469\U0001f3fe\u200d\U0001f9b0"] = ":woman_red_haired_tone4:",
["\U0001f469\U0001f3ff\u200d\U0001f9b0"] = ":woman_red_haired_tone5:",
["\U0001f6a3\u200d\u2640\ufe0f"] = ":woman_rowing_boat:",
["\U0001f6a3\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_rowing_boat_tone1:",
["\U0001f6a3\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_rowing_boat_tone2:",
["\U0001f6a3\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_rowing_boat_tone3:",
["\U0001f6a3\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_rowing_boat_tone4:",
["\U0001f6a3\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_rowing_boat_tone5:",
["\U0001f3c3\u200d\u2640\ufe0f"] = ":woman_running:",
["\U0001f3c3\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_running_tone1:",
["\U0001f3c3\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_running_tone2:",
["\U0001f3c3\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_running_tone3:",
["\U0001f3c3\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_running_tone4:",
["\U0001f3c3\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_running_tone5:",
["\U0001f469\u200d\U0001f52c"] = ":woman_scientist:",
["\U0001f469\U0001f3fb\u200d\U0001f52c"] = ":woman_scientist_tone1:",
["\U0001f469\U0001f3fc\u200d\U0001f52c"] = ":woman_scientist_tone2:",
["\U0001f469\U0001f3fd\u200d\U0001f52c"] = ":woman_scientist_tone3:",
["\U0001f469\U0001f3fe\u200d\U0001f52c"] = ":woman_scientist_tone4:",
["\U0001f469\U0001f3ff\u200d\U0001f52c"] = ":woman_scientist_tone5:",
["\U0001f937\u200d\u2640\ufe0f"] = ":woman_shrugging:",
["\U0001f937\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_shrugging_tone1:",
["\U0001f937\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_shrugging_tone2:",
["\U0001f937\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_shrugging_tone3:",
["\U0001f937\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_shrugging_tone4:",
["\U0001f937\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_shrugging_tone5:",
["\U0001f469\u200d\U0001f3a4"] = ":woman_singer:",
["\U0001f469\U0001f3fb\u200d\U0001f3a4"] = ":woman_singer_tone1:",
["\U0001f469\U0001f3fc\u200d\U0001f3a4"] = ":woman_singer_tone2:",
["\U0001f469\U0001f3fd\u200d\U0001f3a4"] = ":woman_singer_tone3:",
["\U0001f469\U0001f3fe\u200d\U0001f3a4"] = ":woman_singer_tone4:",
["\U0001f469\U0001f3ff\u200d\U0001f3a4"] = ":woman_singer_tone5:",
["\U0001f9cd\u200d\u2640\ufe0f"] = ":woman_standing:",
["\U0001f9cd\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_standing_tone1:",
["\U0001f9cd\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_standing_tone2:",
["\U0001f9cd\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_standing_tone3:",
["\U0001f9cd\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_standing_tone4:",
["\U0001f9cd\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_standing_tone5:",
["\U0001f469\u200d\U0001f393"] = ":woman_student:",
["\U0001f469\U0001f3fb\u200d\U0001f393"] = ":woman_student_tone1:",
["\U0001f469\U0001f3fc\u200d\U0001f393"] = ":woman_student_tone2:",
["\U0001f469\U0001f3fd\u200d\U0001f393"] = ":woman_student_tone3:",
["\U0001f469\U0001f3fe\u200d\U0001f393"] = ":woman_student_tone4:",
["\U0001f469\U0001f3ff\u200d\U0001f393"] = ":woman_student_tone5:",
["\U0001f9b8\u200d\u2640\ufe0f"] = ":woman_superhero:",
["\U0001f9b8\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_superhero_tone1:",
["\U0001f9b8\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_superhero_tone2:",
["\U0001f9b8\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_superhero_tone3:",
["\U0001f9b8\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_superhero_tone4:",
["\U0001f9b8\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_superhero_tone5:",
["\U0001f9b9\u200d\u2640\ufe0f"] = ":woman_supervillain:",
["\U0001f9b9\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_supervillain_tone1:",
["\U0001f9b9\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_supervillain_tone2:",
["\U0001f9b9\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_supervillain_tone3:",
["\U0001f9b9\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_supervillain_tone4:",
["\U0001f9b9\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_supervillain_tone5:",
["\U0001f3c4\u200d\u2640\ufe0f"] = ":woman_surfing:",
["\U0001f3c4\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_surfing_tone1:",
["\U0001f3c4\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_surfing_tone2:",
["\U0001f3c4\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_surfing_tone3:",
["\U0001f3c4\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_surfing_tone4:",
["\U0001f3c4\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_surfing_tone5:",
["\U0001f3ca\u200d\u2640\ufe0f"] = ":woman_swimming:",
["\U0001f3ca\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_swimming_tone1:",
["\U0001f3ca\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_swimming_tone2:",
["\U0001f3ca\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_swimming_tone3:",
["\U0001f3ca\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_swimming_tone4:",
["\U0001f3ca\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_swimming_tone5:",
["\U0001f469\u200d\U0001f3eb"] = ":woman_teacher:",
["\U0001f469\U0001f3fb\u200d\U0001f3eb"] = ":woman_teacher_tone1:",
["\U0001f469\U0001f3fc\u200d\U0001f3eb"] = ":woman_teacher_tone2:",
["\U0001f469\U0001f3fd\u200d\U0001f3eb"] = ":woman_teacher_tone3:",
["\U0001f469\U0001f3fe\u200d\U0001f3eb"] = ":woman_teacher_tone4:",
["\U0001f469\U0001f3ff\u200d\U0001f3eb"] = ":woman_teacher_tone5:",
["\U0001f469\u200d\U0001f4bb"] = ":woman_technologist:",
["\U0001f469\U0001f3fb\u200d\U0001f4bb"] = ":woman_technologist_tone1:",
["\U0001f469\U0001f3fc\u200d\U0001f4bb"] = ":woman_technologist_tone2:",
["\U0001f469\U0001f3fd\u200d\U0001f4bb"] = ":woman_technologist_tone3:",
["\U0001f469\U0001f3fe\u200d\U0001f4bb"] = ":woman_technologist_tone4:",
["\U0001f469\U0001f3ff\u200d\U0001f4bb"] = ":woman_technologist_tone5:",
["\U0001f481\u200d\u2640\ufe0f"] = ":woman_tipping_hand:",
["\U0001f481\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_tipping_hand_tone1:",
["\U0001f481\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_tipping_hand_tone2:",
["\U0001f481\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_tipping_hand_tone3:",
["\U0001f481\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_tipping_hand_tone4:",
["\U0001f481\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_tipping_hand_tone5:",
["\U0001f469\U0001f3fb"] = ":woman_tone1:",
["\U0001f469\U0001f3fc"] = ":woman_tone2:",
["\U0001f469\U0001f3fd"] = ":woman_tone3:",
["\U0001f469\U0001f3fe"] = ":woman_tone4:",
["\U0001f469\U0001f3ff"] = ":woman_tone5:",
["\U0001f9db\u200d\u2640\ufe0f"] = ":woman_vampire:",
["\U0001f9db\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_vampire_tone1:",
["\U0001f9db\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_vampire_tone2:",
["\U0001f9db\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_vampire_tone3:",
["\U0001f9db\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_vampire_tone4:",
["\U0001f9db\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_vampire_tone5:",
["\U0001f6b6\u200d\u2640\ufe0f"] = ":woman_walking:",
["\U0001f6b6\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_walking_tone1:",
["\U0001f6b6\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_walking_tone2:",
["\U0001f6b6\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_walking_tone3:",
["\U0001f6b6\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_walking_tone4:",
["\U0001f6b6\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_walking_tone5:",
["\U0001f473\u200d\u2640\ufe0f"] = ":woman_wearing_turban:",
["\U0001f473\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_wearing_turban_tone1:",
["\U0001f473\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_wearing_turban_tone2:",
["\U0001f473\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_wearing_turban_tone3:",
["\U0001f473\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_wearing_turban_tone4:",
["\U0001f473\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_wearing_turban_tone5:",
["\U0001f469\u200d\U0001f9b3"] = ":woman_white_haired:",
["\U0001f469\U0001f3fb\u200d\U0001f9b3"] = ":woman_white_haired_tone1:",
["\U0001f469\U0001f3fc\u200d\U0001f9b3"] = ":woman_white_haired_tone2:",
["\U0001f469\U0001f3fd\u200d\U0001f9b3"] = ":woman_white_haired_tone3:",
["\U0001f469\U0001f3fe\u200d\U0001f9b3"] = ":woman_white_haired_tone4:",
["\U0001f469\U0001f3ff\u200d\U0001f9b3"] = ":woman_white_haired_tone5:",
["\U0001f9d5"] = ":woman_with_headscarf:",
["\U0001f9d5\U0001f3fb"] = ":woman_with_headscarf_tone1:",
["\U0001f9d5\U0001f3fc"] = ":woman_with_headscarf_tone2:",
["\U0001f9d5\U0001f3fd"] = ":woman_with_headscarf_tone3:",
["\U0001f9d5\U0001f3fe"] = ":woman_with_headscarf_tone4:",
["\U0001f9d5\U0001f3ff"] = ":woman_with_headscarf_tone5:",
["\U0001f469\u200d\U0001f9af"] = ":woman_with_probing_cane:",
["\U0001f469\U0001f3fb\u200d\U0001f9af"] = ":woman_with_probing_cane_tone1:",
["\U0001f469\U0001f3fc\u200d\U0001f9af"] = ":woman_with_probing_cane_tone2:",
["\U0001f469\U0001f3fd\u200d\U0001f9af"] = ":woman_with_probing_cane_tone3:",
["\U0001f469\U0001f3fe\u200d\U0001f9af"] = ":woman_with_probing_cane_tone4:",
["\U0001f469\U0001f3ff\u200d\U0001f9af"] = ":woman_with_probing_cane_tone5:",
["\U0001f470\u200d\u2640\ufe0f"] = ":woman_with_veil:",
["\U0001f470\U0001f3fb\u200d\u2640\ufe0f"] = ":woman_with_veil_tone1:",
["\U0001f470\U0001f3fc\u200d\u2640\ufe0f"] = ":woman_with_veil_tone2:",
["\U0001f470\U0001f3fd\u200d\u2640\ufe0f"] = ":woman_with_veil_tone3:",
["\U0001f470\U0001f3fe\u200d\u2640\ufe0f"] = ":woman_with_veil_tone4:",
["\U0001f470\U0001f3ff\u200d\u2640\ufe0f"] = ":woman_with_veil_tone5:",
["\U0001f9df\u200d\u2640\ufe0f"] = ":woman_zombie:",
["\U0001f45a"] = ":womans_clothes:",
["\U0001f97f"] = ":womans_flat_shoe:",
["\U0001f452"] = ":womans_hat:",
["\U0001f46d"] = ":women_holding_hands_tone5_tone4:",
["\U0001f46f\u200d\u2640\ufe0f"] = ":women_with_bunny_ears_partying:",
["\U0001f93c\u200d\u2640\ufe0f"] = ":women_wrestling:",
["\U0001f6ba"] = ":womens:",
["\U0001fab5"] = ":wood:",
["\U0001f974"] = ":woozy_face:",
["\U0001fab1"] = ":worm:",
["\U0001f61f"] = ":worried:",
["\U0001f527"] = ":wrench:",
["\u270d\ufe0f"] = ":writing_hand:",
["\u270d"] = ":writing_hand:",
["\u270d\U0001f3fb"] = ":writing_hand_tone1:",
["\u270d\U0001f3fc"] = ":writing_hand_tone2:",
["\u270d\U0001f3fd"] = ":writing_hand_tone3:",
["\u270d\U0001f3fe"] = ":writing_hand_tone4:",
["\u270d\U0001f3ff"] = ":writing_hand_tone5:",
["\u274c"] = ":x:",
["\U0001f9f6"] = ":yarn:",
["\U0001f971"] = ":yawning_face:",
["\U0001f7e1"] = ":yellow_circle:",
["\U0001f49b"] = ":yellow_heart:",
["\U0001f7e8"] = ":yellow_square:",
["\U0001f4b4"] = ":yen:",
["\u262f\ufe0f"] = ":yin_yang:",
["\u262f"] = ":yin_yang:",
["\U0001fa80"] = ":yo_yo:",
["\U0001f60b"] = ":yum:",
["\U0001f92a"] = ":zany_face:",
["\u26a1"] = ":zap:",
["\U0001f993"] = ":zebra:",
["\u0030\ufe0f\u20e3"] = ":zero:",
["\u0030\u20e3"] = ":zero:",
["\U0001f910"] = ":zipper_mouth:",
["\U0001f9df"] = ":zombie:",
["\U0001f4a4"] = ":zzz:",
};
#endregion
}
}
diff --git a/DisCatSharp/Entities/Emoji/DiscordEmoji.cs b/DisCatSharp/Entities/Emoji/DiscordEmoji.cs
index 776afe28f..37ab73e53 100644
--- a/DisCatSharp/Entities/Emoji/DiscordEmoji.cs
+++ b/DisCatSharp/Entities/Emoji/DiscordEmoji.cs
@@ -1,375 +1,375 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a Discord emoji.
/// </summary>
public partial class DiscordEmoji : SnowflakeObject, IEquatable<DiscordEmoji>
{
/// <summary>
/// Gets the name of this emoji.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; internal set; }
/// <summary>
/// Gets IDs the roles this emoji is enabled for.
/// </summary>
[JsonIgnore]
public IReadOnlyList<ulong> Roles => this._rolesLazy.Value;
[JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)]
public List<ulong> RolesInternal;
private readonly Lazy<IReadOnlyList<ulong>> _rolesLazy;
/// <summary>
/// Gets whether this emoji requires colons to use.
/// </summary>
[JsonProperty("require_colons")]
public bool RequiresColons { get; internal set; }
/// <summary>
/// Gets whether this emoji is managed by an integration.
/// </summary>
[JsonProperty("managed")]
public bool IsManaged { get; internal set; }
/// <summary>
/// Gets whether this emoji is animated.
/// </summary>
[JsonProperty("animated")]
public bool IsAnimated { get; internal set; }
/// <summary>
/// Gets whether the emoji is available for use.
/// An emoji may not be available due to loss of server boost.
/// </summary>
[JsonProperty("available", NullValueHandling = NullValueHandling.Ignore)]
public bool IsAvailable { get; internal set; }
/// <summary>
/// Gets the image URL of this emoji.
/// </summary>
[JsonIgnore]
public string Url =>
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";
/// <summary>
/// Initializes a new instance of the <see cref="DiscordEmoji"/> class.
/// </summary>
internal DiscordEmoji()
{
this._rolesLazy = new Lazy<IReadOnlyList<ulong>>(() => new ReadOnlyCollection<ulong>(this.RolesInternal));
}
/// <summary>
/// Gets emoji's name in non-Unicode format (eg. :thinking: instead of the Unicode representation of the emoji).
/// </summary>
public string GetDiscordName()
{
s_discordNameLookup.TryGetValue(this.Name, out var name);
return name ?? $":{this.Name}:";
}
/// <summary>
/// Returns a string representation of this emoji.
/// </summary>
/// <returns>String representation of this emoji.</returns>
public override string ToString()
=> this.Id != 0
? this.IsAnimated
? $"<a:{this.Name}:{this.Id.ToString(CultureInfo.InvariantCulture)}>"
: $"<:{this.Name}:{this.Id.ToString(CultureInfo.InvariantCulture)}>"
: this.Name;
/// <summary>
/// Checks whether this <see cref="DiscordEmoji"/> is equal to another object.
/// </summary>
/// <param name="obj">Object to compare to.</param>
/// <returns>Whether the object is equal to this <see cref="DiscordEmoji"/>.</returns>
public override bool Equals(object obj)
=> this.Equals(obj as DiscordEmoji);
/// <summary>
/// Checks whether this <see cref="DiscordEmoji"/> is equal to another <see cref="DiscordEmoji"/>.
/// </summary>
/// <param name="e"><see cref="DiscordEmoji"/> to compare to.</param>
/// <returns>Whether the <see cref="DiscordEmoji"/> is equal to this <see cref="DiscordEmoji"/>.</returns>
public bool Equals(DiscordEmoji e)
=> e is not null && (ReferenceEquals(this, e) || (this.Id == e.Id && this.Name == e.Name));
/// <summary>
/// Gets the hash code for this <see cref="DiscordEmoji"/>.
/// </summary>
/// <returns>The hash code for this <see cref="DiscordEmoji"/>.</returns>
public override int GetHashCode()
{
var hash = 13;
hash = (hash * 7) + this.Id.GetHashCode();
hash = (hash * 7) + this.Name.GetHashCode();
return hash;
}
/// <summary>
/// Gets the reactions string.
/// </summary>
internal string ToReactionString()
=> this.Id != 0 ? $"{this.Name}:{this.Id.ToString(CultureInfo.InvariantCulture)}" : this.Name;
/// <summary>
/// Gets whether the two <see cref="DiscordEmoji"/> objects are equal.
/// </summary>
/// <param name="e1">First emoji to compare.</param>
/// <param name="e2">Second emoji to compare.</param>
/// <returns>Whether the two emoji are equal.</returns>
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);
}
/// <summary>
/// Gets whether the two <see cref="DiscordEmoji"/> objects are not equal.
/// </summary>
/// <param name="e1">First emoji to compare.</param>
/// <param name="e2">Second emoji to compare.</param>
/// <returns>Whether the two emoji are not equal.</returns>
public static bool operator !=(DiscordEmoji e1, DiscordEmoji e2)
=> !(e1 == e2);
/// <summary>
/// Implicitly converts this emoji to its string representation.
/// </summary>
/// <param name="e1">Emoji to convert.</param>
public static implicit operator string(DiscordEmoji e1)
=> e1.ToString();
/// <summary>
/// Checks whether specified unicode entity is a valid unicode emoji.
/// </summary>
/// <param name="unicodeEntity">Entity to check.</param>
/// <returns>Whether it's a valid emoji.</returns>
public static bool IsValidUnicode(string unicodeEntity)
=> s_discordNameLookup.ContainsKey(unicodeEntity);
/// <summary>
/// Creates an emoji object from a unicode entity.
/// </summary>
/// <param name="client"><see cref="BaseDiscordClient"/> to attach to the object.</param>
/// <param name="unicodeEntity">Unicode entity to create the object from.</param>
/// <returns>Create <see cref="DiscordEmoji"/> object.</returns>
public static DiscordEmoji FromUnicode(BaseDiscordClient client, string unicodeEntity)
=> !IsValidUnicode(unicodeEntity)
? throw new ArgumentException("Specified unicode entity is not a valid unicode emoji.", nameof(unicodeEntity))
: new DiscordEmoji { Name = unicodeEntity, Discord = client };
/// <summary>
/// Creates an emoji object from a unicode entity.
/// </summary>
/// <param name="unicodeEntity">Unicode entity to create the object from.</param>
/// <returns>Create <see cref="DiscordEmoji"/> object.</returns>
public static DiscordEmoji FromUnicode(string unicodeEntity)
=> FromUnicode(null, unicodeEntity);
/// <summary>
/// Attempts to create an emoji object from a unicode entity.
/// </summary>
/// <param name="client"><see cref="BaseDiscordClient"/> to attach to the object.</param>
/// <param name="unicodeEntity">Unicode entity to create the object from.</param>
/// <param name="emoji">Resulting <see cref="DiscordEmoji"/> object.</param>
/// <returns>Whether the operation was successful.</returns>
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 (!s_discordNameLookup.TryGetValue(unicodeEntity, out var discordName))
return false;
if (!s_unicodeEmojis.TryGetValue(discordName, out unicodeEntity))
return false;
emoji = new DiscordEmoji { Name = unicodeEntity, Discord = client };
return true;
}
/// <summary>
/// Attempts to create an emoji object from a unicode entity.
/// </summary>
/// <param name="unicodeEntity">Unicode entity to create the object from.</param>
/// <param name="emoji">Resulting <see cref="DiscordEmoji"/> object.</param>
/// <returns>Whether the operation was successful.</returns>
public static bool TryFromUnicode(string unicodeEntity, out DiscordEmoji emoji)
=> TryFromUnicode(null, unicodeEntity, out emoji);
/// <summary>
/// Creates an emoji object from a guild emote.
/// </summary>
/// <param name="client"><see cref="BaseDiscordClient"/> to attach to the object.</param>
/// <param name="id">Id of the emote.</param>
/// <returns>Create <see cref="DiscordEmoji"/> object.</returns>
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.");
}
/// <summary>
/// Attempts to create an emoji object from a guild emote.
/// </summary>
/// <param name="client"><see cref="BaseDiscordClient"/> to attach to the object.</param>
/// <param name="id">Id of the emote.</param>
/// <param name="emoji">Resulting <see cref="DiscordEmoji"/> object.</param>
/// <returns>Whether the operation was successful.</returns>
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;
}
/// <summary>
/// Creates 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:).
/// </summary>
/// <param name="client"><see cref="BaseDiscordClient"/> to attach to the object.</param>
/// <param name="name">Name of the emote to find, including colons (eg. :thinking:).</param>
/// <param name="includeGuilds">Should guild emojis be included in the search.</param>
/// <returns>Create <see cref="DiscordEmoji"/> object.</returns>
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 (s_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));
}
/// <summary>
/// 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:).
/// </summary>
/// <param name="client"><see cref="BaseDiscordClient"/> to attach to the object.</param>
/// <param name="name">Name of the emote to find, including colons (eg. :thinking:).</param>
/// <param name="emoji">Resulting <see cref="DiscordEmoji"/> object.</param>
/// <returns>Whether the operation was successful.</returns>
public static bool TryFromName(BaseDiscordClient client, string name, out DiscordEmoji emoji)
=> TryFromName(client, name, true, out emoji);
/// <summary>
/// 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:).
/// </summary>
/// <param name="client"><see cref="BaseDiscordClient"/> to attach to the object.</param>
/// <param name="name">Name of the emote to find, including colons (eg. :thinking:).</param>
/// <param name="includeGuilds">Should guild emojis be included in the search.</param>
/// <param name="emoji">Resulting <see cref="DiscordEmoji"/> object.</param>
/// <returns>Whether the operation was successful.</returns>
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 (s_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 f68892127..1e463d97d 100644
--- a/DisCatSharp/Entities/Emoji/DiscordUnicodeEmoji.cs
+++ b/DisCatSharp/Entities/Emoji/DiscordUnicodeEmoji.cs
@@ -1,2147 +1,2147 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a discord unicode emoji.
/// </summary>
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 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 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 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 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 U55_B6 = "\U0001f23a";
public const string U6307 = "\U0001f22f";
public const string U6708 = "\U0001f237";
public const string U6709 = "\U0001f236";
public const string U6_E80 = "\U0001f235";
public const string U7121 = "\U0001f21a";
public const string U7533 = "\U0001f238";
public const string U7981 = "\U0001f232";
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 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 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/Automod/AutomodAction.cs b/DisCatSharp/Entities/Guild/Automod/AutomodAction.cs
index 786b44455..f716bbcc3 100644
--- a/DisCatSharp/Entities/Guild/Automod/AutomodAction.cs
+++ b/DisCatSharp/Entities/Guild/Automod/AutomodAction.cs
@@ -1,62 +1,62 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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
{
/// <summary>
/// Represents an action which will execute when a rule is triggered.
/// </summary>
public class AutomodAction
{
/// <summary>
/// The type of action.
/// </summary>
[JsonProperty("type")]
public AutomodActionType ActionType { get; internal set; }
/// <summary>
/// The additional meta data needed during execution for this specific action type.
/// </summary>
[JsonProperty("metadata", NullValueHandling = NullValueHandling.Ignore)]
public AutomodActionMetadata Metadata { get; internal set; }
/// <summary>
/// Creates a new empty automod action.
/// </summary>
internal AutomodAction() { }
/// <summary>
/// Creates a new automod action.
/// </summary>
/// <param name="actionType">The type of action.</param>
/// <param name="metadata">The additional metadata for this action.</param>
public AutomodAction(AutomodActionType actionType, AutomodActionMetadata metadata = null)
{
this.ActionType = actionType;
this.Metadata = metadata;
}
}
}
diff --git a/DisCatSharp/Entities/Guild/Automod/AutomodActionMetadata.cs b/DisCatSharp/Entities/Guild/Automod/AutomodActionMetadata.cs
index d914d8a69..0561bafe1 100644
--- a/DisCatSharp/Entities/Guild/Automod/AutomodActionMetadata.cs
+++ b/DisCatSharp/Entities/Guild/Automod/AutomodActionMetadata.cs
@@ -1,47 +1,47 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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
{
/// <summary>
/// Additional data used when an action is executed.
/// Different fields are relevant based on the action type.
/// </summary>
public class AutomodActionMetadata
{
/// <summary>
/// The channel to which user content should be logged.
/// Only works with SendAlertMessage.
/// </summary>
[JsonProperty("channel_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? ChannelId { get; internal set; }
/// <summary>
/// The timeout duration in seconds.
/// Maximum of 2419200 seconds (4 weeks).
/// </summary>
[JsonProperty("duration_seconds", NullValueHandling = NullValueHandling.Ignore)]
public int? Duration { get; internal set; }
}
}
diff --git a/DisCatSharp/Entities/Guild/Automod/AutomodRule.cs b/DisCatSharp/Entities/Guild/Automod/AutomodRule.cs
index 16e470be8..5576a9307 100644
--- a/DisCatSharp/Entities/Guild/Automod/AutomodRule.cs
+++ b/DisCatSharp/Entities/Guild/Automod/AutomodRule.cs
@@ -1,150 +1,150 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Exceptions;
using DisCatSharp.Net.Models;
using Newtonsoft.Json;
namespace DisCatSharp.Entities
{
/// <summary>
/// Represents an auto mod rule.
/// </summary>
public class AutomodRule : SnowflakeObject
{
/// <summary>
/// Gets the id of the guild this rule belongs to.
/// </summary>
[JsonProperty("guild_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong GuildId { get; internal set; }
/// <summary>
/// Gets the name of this rule.
/// </summary>
[JsonProperty("name")]
public string Name { get; internal set; }
/// <summary>
/// The id of the user who first created this rule.
/// </summary>
[JsonProperty("creator_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong CreatorId { get; internal set; }
/// <summary>
/// Gets the type of this rule.
/// </summary>
[JsonProperty("event_type")]
public AutomodEventType EventType { get; internal set; }
/// <summary>
/// Gets the trigger type of this rule.
/// </summary>
[JsonProperty("trigger_type")]
public AutomodTriggerType TriggerType { get; internal set; }
/// <summary>
/// Gets the rule trigger meta data.
/// </summary>
[JsonProperty("trigger_metadata", NullValueHandling = NullValueHandling.Ignore)]
public AutomodTriggerMetadata TriggerMetadata { get; internal set; }
/// <summary>
/// The actions which will execute when the rule is triggered.
/// </summary>
[JsonProperty("actions")]
public IReadOnlyList<AutomodAction> Actions { get; internal set; }
/// <summary>
/// Whether the rule is enabled.
/// </summary>
[JsonProperty("enabled")]
public bool Enabled { get; internal set; }
/// <summary>
/// The role ids that should not be affected by the rule.
/// Maximum of 20.
/// </summary>
[JsonProperty("exempt_roles", NullValueHandling = NullValueHandling.Ignore)]
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public IReadOnlyList<ulong>? ExemptRoles { get; internal set; }
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
/// <summary>
/// The channel ids that should not be affected by the rule.
/// Maximum of 50.
/// </summary>
[JsonProperty("exempt_channels", NullValueHandling = NullValueHandling.Ignore)]
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public IReadOnlyList<ulong>? ExemptChannels { get; internal set; }
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
[JsonIgnore]
public DiscordGuild Guild
=> this.Discord.Guilds.TryGetValue(this.GuildId, out var guild) ? guild : null;
/// <summary>
/// Modifies this auto mod rule.
/// </summary>
/// <param name="action">Action to perform on this rule.</param>
/// <returns>The modified rule object.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageGuild"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the rule does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<AutomodRule> ModifyAsync(Action<AutomodRuleEditModel> action)
{
var mdl = new AutomodRuleEditModel();
action(mdl);
if (mdl.TriggerMetadata.HasValue)
{
if ((mdl.TriggerMetadata.Value.KeywordFilter != null || mdl.TriggerMetadata.Value.RegexPatterns != null) && this.TriggerType != AutomodTriggerType.Keyword)
throw new ArgumentException($"Cannot use KeywordFilter and RegexPattern for a {this.TriggerType} rule. Only {AutomodTriggerType.Keyword} is valid in this context.");
else if (mdl.TriggerMetadata.Value.AllowList != null && this.TriggerType != AutomodTriggerType.KeywordPreset)
throw new ArgumentException($"Cannot use AllowList for a {this.TriggerType} rule. Only {AutomodTriggerType.KeywordPreset} is valid in this context.");
else if (mdl.TriggerMetadata.Value.MentionTotalLimit != null && this.TriggerType != AutomodTriggerType.MentionSpam)
throw new ArgumentException($"Cannot use MentionTotalLimit for a {this.TriggerType} rule. Only {AutomodTriggerType.MentionSpam} is valid in this context.");
if (mdl.TriggerMetadata.Value.MentionRaidProtectionEnabled != null && this.TriggerType != AutomodTriggerType.MentionSpam)
throw new ArgumentException($"Cannot use MentionRaidProtectionEnabled for a {this.TriggerType} rule. Only {AutomodTriggerType.MentionSpam} is valid in this context.");
}
return await this.Discord.ApiClient.ModifyAutomodRuleAsync(this.GuildId, this.Id, mdl.Name, mdl.EventType, mdl.TriggerMetadata, mdl.Actions, mdl.Enabled, mdl.ExemptRoles, mdl.ExemptChannels, mdl.AuditLogReason);
}
/// <summary>
/// Deletes this auto mod rule.
/// </summary>
/// <param name="reason">The reason for this deletion.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageGuild"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the rule does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task DeleteAsync(string reason = null)
=> await this.Discord.ApiClient.DeleteAutomodRuleAsync(this.GuildId, this.Id, reason);
}
}
diff --git a/DisCatSharp/Entities/Guild/Automod/AutomodTriggerMetadata.cs b/DisCatSharp/Entities/Guild/Automod/AutomodTriggerMetadata.cs
index df8714f77..6ccac18b4 100644
--- a/DisCatSharp/Entities/Guild/Automod/AutomodTriggerMetadata.cs
+++ b/DisCatSharp/Entities/Guild/Automod/AutomodTriggerMetadata.cs
@@ -1,86 +1,86 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Collections.ObjectModel;
using DisCatSharp.Attributes;
using DisCatSharp.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities
{
/// <summary>
/// Represents the rule's meta data.
/// </summary>
public class AutomodTriggerMetadata
{
/// <summary>
/// <para>The substrings which will be searched for in content. Maximum of 1000.</para>
/// <para>A keyword can be a phrase which contains multiple words. Wildcard symbols (not available to allow lists) can be used to customize how each keyword will be matched.</para>
/// <para>See <see href="https://discord.com/developers/docs/resources/auto-moderation#auto-moderation-rule-object-keyword-matching-strategies">keyword matching strategies</see>. Each keyword must be 30 characters or less.</para>
/// </summary>
[JsonProperty("keyword_filter", NullValueHandling = NullValueHandling.Ignore)]
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public ReadOnlyCollection<string>? KeywordFilter { get; set; }
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
/// <summary>
/// The internally predefined word-sets which will be searched for in content.
/// </summary>
[JsonProperty("presets", NullValueHandling = NullValueHandling.Ignore)]
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public IReadOnlyList<AutomodKeywordPresetType>? Presets { get; internal set; }
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
/// <summary>
/// <para>The regex patterns the content will be checked against. Maximum of 10.</para>
/// <para>Only Rust flavored regex is currently supported, which can be tested in online editors such as <see href="https://rustexp.lpil.uk/">Rustexp</see>.</para>
/// <para> Each regex pattern must be 75 characters or less.</para>
/// </summary>
[JsonProperty("regex_patterns", NullValueHandling = NullValueHandling.Ignore)]
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public List<string>? RegexPatterns { get; set; }
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
/// <summary>
/// The substrings which will be exempt from triggering the preset type. Maximum of 1000.
/// </summary>
[JsonProperty("allow_list", NullValueHandling = NullValueHandling.Ignore)]
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public List<string>? AllowList { get; set; }
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
/// <summary>
/// The total number of unique role and user mentions allowed per message. Maximum of 50.
/// </summary>
[JsonProperty("mention_total_limit", NullValueHandling = NullValueHandling.Ignore)]
public int? MentionTotalLimit { get; set; } = null;
/// <summary>
/// Whether to automatically detect mention raids.
/// </summary>
[JsonProperty("mention_raid_protection_enabled", NullValueHandling = NullValueHandling.Ignore), DiscordInExperiment]
public bool? MentionRaidProtectionEnabled { get; set; } = null;
}
}
diff --git a/DisCatSharp/Entities/Guild/DiscordAuditLogObjects.cs b/DisCatSharp/Entities/Guild/DiscordAuditLogObjects.cs
index e362eaa0e..490e754ed 100644
--- a/DisCatSharp/Entities/Guild/DiscordAuditLogObjects.cs
+++ b/DisCatSharp/Entities/Guild/DiscordAuditLogObjects.cs
@@ -1,873 +1,873 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using DisCatSharp.Enums;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents an audit log entry.
/// </summary>
public abstract class DiscordAuditLogEntry : SnowflakeObject
{
/// <summary>
/// Gets the entry's action type.
/// </summary>
public AuditLogActionType ActionType { get; internal set; }
/// <summary>
/// Gets the user responsible for the action.
/// </summary>
public DiscordUser UserResponsible { get; internal set; }
/// <summary>
/// Gets the reason defined in the action.
/// </summary>
public string Reason { get; internal set; }
/// <summary>
/// Gets the category under which the action falls.
/// </summary>
public AuditLogActionCategory ActionCategory { get; internal set; }
}
/// <summary>
/// Represents a description of how a property changed.
/// </summary>
/// <typeparam name="T">Type of the changed property.</typeparam>
public sealed class PropertyChange<T>
{
/// <summary>
/// The property's value before it was changed.
/// </summary>
public T Before { get; internal set; }
/// <summary>
/// The property's value after it was changed.
/// </summary>
public T After { get; internal set; }
}
/// <summary>
/// Represents a audit log guild entry.
/// </summary>
public sealed class DiscordAuditLogGuildEntry : DiscordAuditLogEntry
{
/// <summary>
/// Gets the affected guild.
/// </summary>
public DiscordGuild Target { get; internal set; }
/// <summary>
/// <see cref="DiscordGuild.Name"/>
/// </summary>
public PropertyChange<string> NameChange { get; internal set; }
/// <summary>
/// <see cref="DiscordGuild.Owner"/>
/// </summary>
public PropertyChange<DiscordMember> OwnerChange { get; internal set; }
/// <summary>
/// <see cref="DiscordGuild.IconUrl"/>
/// </summary>
public PropertyChange<string> IconChange { get; internal set; }
/// <summary>
/// <see cref="DiscordGuild.VerificationLevel"/>
/// </summary>
public PropertyChange<VerificationLevel> VerificationLevelChange { get; internal set; }
/// <summary>
/// <see cref="DiscordGuild.AfkChannel"/>
/// </summary>
public PropertyChange<DiscordChannel> AfkChannelChange { get; internal set; }
/// <summary>
/// <see cref="DiscordGuild.SystemChannelFlags"/>
/// </summary>
public PropertyChange<SystemChannelFlags> SystemChannelFlagsChange { get; internal set; }
/// <summary>
/// <see cref="DiscordGuild.WidgetChannel"/>
/// </summary>
public PropertyChange<DiscordChannel> WidgetChannelChange { get; internal set; }
[Obsolete("Use properly named WidgetChannelChange")]
public PropertyChange<DiscordChannel> EmbedChannelChange => this.WidgetChannelChange;
/// <summary>
/// <see cref="DiscordGuild.RulesChannel"/>
/// </summary>
public PropertyChange<DiscordChannel> RulesChannelChange { get; internal set; }
/// <summary>
/// <see cref="DiscordGuild.PublicUpdatesChannel"/>
/// </summary>
public PropertyChange<DiscordChannel> PublicUpdatesChannelChange { get; internal set; }
/// <summary>
/// <see cref="DiscordGuild.DefaultMessageNotifications"/>
/// </summary>
public PropertyChange<DefaultMessageNotifications> NotificationSettingsChange { get; internal set; }
/// <summary>
/// <see cref="DiscordGuild.SystemChannel"/>
/// </summary>
public PropertyChange<DiscordChannel> SystemChannelChange { get; internal set; }
/// <summary>
/// <see cref="DiscordGuild.ExplicitContentFilter"/>
/// </summary>
public PropertyChange<ExplicitContentFilter> ExplicitContentFilterChange { get; internal set; }
/// <summary>
/// <see cref="DiscordGuild.MfaLevel"/>
/// </summary>
public PropertyChange<MfaLevel> MfaLevelChange { get; internal set; }
/// <summary>
/// <see cref="DiscordGuild.SplashUrl"/>
/// </summary>
public PropertyChange<string> SplashChange { get; internal set; }
/// <summary>
/// <see cref="DiscordGuild.VoiceRegion"/>
/// </summary>
public PropertyChange<string> RegionChange { get; internal set; }
/// <summary>
/// <see cref="DiscordGuild.VanityUrlCode"/>
/// </summary>
public PropertyChange<string> VanityUrlCodeChange { get; internal set; }
/// <summary>
/// <see cref="DiscordGuild.PremiumProgressBarEnabled"/>
/// </summary>
public PropertyChange<bool> PremiumProgressBarChange { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordAuditLogGuildEntry"/> class.
/// </summary>
internal DiscordAuditLogGuildEntry() { }
}
/// <summary>
/// Represents a audit log channel entry.
/// </summary>
public sealed class DiscordAuditLogChannelEntry : DiscordAuditLogEntry
{
/// <summary>
/// Gets the affected channel.
/// </summary>
public DiscordChannel Target { get; internal set; }
/// <summary>
/// Gets the description of channel's name change.
/// </summary>
public PropertyChange<string> NameChange { get; internal set; }
/// <summary>
/// Gets the description of channel's type change.
/// </summary>
public PropertyChange<ChannelType?> TypeChange { get; internal set; }
/// <summary>
/// Gets the description of channel's nsfw flag change.
/// </summary>
public PropertyChange<bool?> NsfwChange { get; internal set; }
/// <summary>
/// <see cref="DiscordChannel.RtcRegionId"/>
/// </summary>
public PropertyChange<string> RtcRegionIdChange { get; internal set; }
/// <summary>
/// Gets the description of channel's bitrate change.
/// </summary>
public PropertyChange<int?> BitrateChange { get; internal set; }
/// <summary>
/// Gets the description of channel permission overwrites' change.
/// </summary>
public PropertyChange<IReadOnlyList<DiscordOverwrite>> OverwriteChange { get; internal set; }
/// <summary>
/// Gets the description of channel's topic change.
/// </summary>
public PropertyChange<string> TopicChange { get; internal set; }
/// <summary>
/// <see cref="DiscordChannel.UserLimit"/>
/// </summary>
public PropertyChange<int?> UserLimitChange { get; internal set; }
/// <summary>
/// Gets the description of channel's slow mode timeout change.
/// </summary>
public PropertyChange<int?> PerUserRateLimitChange { get; internal set; }
/// <summary>
/// Gets the channel flags change.
/// </summary>
public PropertyChange<ChannelFlags> ChannelFlagsChange { get; internal set; }
/// <summary>
/// <see cref="DiscordChannel.DefaultAutoArchiveDuration"/>
/// </summary>
public PropertyChange<ThreadAutoArchiveDuration?> DefaultAutoArchiveDurationChange { get; internal set; }
/// <summary>
/// <see cref="DiscordChannel.InternalAvailableTags"/>
/// </summary>
public PropertyChange<List<ForumPostTag>> AvailableTagsChange { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordAuditLogChannelEntry"/> class.
/// </summary>
internal DiscordAuditLogChannelEntry() { }
}
/// <summary>
/// Represents a audit log overwrite entry.
/// </summary>
public sealed class DiscordAuditLogOverwriteEntry : DiscordAuditLogEntry
{
/// <summary>
/// Gets the affected overwrite.
/// </summary>
public DiscordOverwrite Target { get; internal set; }
/// <summary>
/// Gets the channel for which the overwrite was changed.
/// </summary>
public DiscordChannel Channel { get; internal set; }
/// <summary>
/// Gets the description of overwrite's allow value change.
/// </summary>
public PropertyChange<Permissions?> AllowChange { get; internal set; }
/// <summary>
/// Gets the description of overwrite's deny value change.
/// </summary>
public PropertyChange<Permissions?> DenyChange { get; internal set; }
/// <summary>
/// Gets the description of overwrite's type change.
/// </summary>
public PropertyChange<OverwriteType?> TypeChange { get; internal set; }
/// <summary>
/// Gets the description of overwrite's target id change.
/// </summary>
public PropertyChange<ulong?> TargetIdChange { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordAuditLogOverwriteEntry"/> class.
/// </summary>
internal DiscordAuditLogOverwriteEntry() { }
}
/// <summary>
/// Represents a audit log kick entry.
/// </summary>
public sealed class DiscordAuditLogKickEntry : DiscordAuditLogEntry
{
/// <summary>
/// Gets the kicked member.
/// </summary>
public DiscordMember Target { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordAuditLogKickEntry"/> class.
/// </summary>
internal DiscordAuditLogKickEntry() { }
}
/// <summary>
/// Represents a audit log prune entry.
/// </summary>
public sealed class DiscordAuditLogPruneEntry : DiscordAuditLogEntry
{
/// <summary>
/// Gets the number inactivity days after which members were pruned.
/// </summary>
public int Days { get; internal set; }
/// <summary>
/// Gets the number of members pruned.
/// </summary>
public int Toll { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordAuditLogPruneEntry"/> class.
/// </summary>
internal DiscordAuditLogPruneEntry() { }
}
/// <summary>
/// Represents a audit log ban entry.
/// </summary>
public sealed class DiscordAuditLogBanEntry : DiscordAuditLogEntry
{
/// <summary>
/// Gets the banned member.
/// </summary>
public DiscordMember Target { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordAuditLogBanEntry"/> class.
/// </summary>
internal DiscordAuditLogBanEntry() { }
}
/// <summary>
/// Represents a audit log member update entry.
/// </summary>
public sealed class DiscordAuditLogMemberUpdateEntry : DiscordAuditLogEntry
{
/// <summary>
/// Gets the affected member.
/// </summary>
public DiscordMember Target { get; internal set; }
/// <summary>
/// Gets the description of member's nickname change.
/// </summary>
public PropertyChange<string> NicknameChange { get; internal set; }
/// <summary>
/// Gets the roles that were removed from the member.
/// </summary>
public IReadOnlyList<DiscordRole> RemovedRoles { get; internal set; }
/// <summary>
/// Gets the roles that were added to the member.
/// </summary>
public IReadOnlyList<DiscordRole> AddedRoles { get; internal set; }
/// <summary>
/// Gets the description of member's mute status change.
/// </summary>
public PropertyChange<bool?> MuteChange { get; internal set; }
/// <summary>
/// Gets the description of member's deaf status change.
/// </summary>
public PropertyChange<bool?> DeafenChange { get; internal set; }
/// <summary>
/// Get's the timeout change.
/// </summary>
public PropertyChange<DateTime?> CommunicationDisabledUntilChange { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordAuditLogMemberUpdateEntry"/> class.
/// </summary>
internal DiscordAuditLogMemberUpdateEntry() { }
}
/// <summary>
/// Represents a audit log role update entry.
/// </summary>
public sealed class DiscordAuditLogRoleUpdateEntry : DiscordAuditLogEntry
{
/// <summary>
/// Gets the affected role.
/// </summary>
public DiscordRole Target { get; internal set; }
/// <summary>
/// Gets the description of role's name change.
/// </summary>
public PropertyChange<string> NameChange { get; internal set; }
/// <summary>
/// Gets the description of role's color change.
/// </summary>
public PropertyChange<int?> ColorChange { get; internal set; }
/// <summary>
/// Gets the description of role's permission set change.
/// </summary>
public PropertyChange<Permissions?> PermissionChange { get; internal set; }
/// <summary>
/// Gets the description of the role's position change.
/// </summary>
public PropertyChange<int?> PositionChange { get; internal set; }
/// <summary>
/// Gets the description of the role's mentionability change.
/// </summary>
public PropertyChange<bool?> MentionableChange { get; internal set; }
/// <summary>
/// Gets the description of the role's hoist status change.
/// </summary>
public PropertyChange<bool?> HoistChange { get; internal set; }
/// <summary>
/// <see cref="DiscordRole.IconHash"/>
/// </summary>
public PropertyChange<string> IconHashChange { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordAuditLogRoleUpdateEntry"/> class.
/// </summary>
internal DiscordAuditLogRoleUpdateEntry() { }
}
/// <summary>
/// Represents a audit log invite entry.
/// </summary>
public sealed class DiscordAuditLogInviteEntry : DiscordAuditLogEntry
{
/// <summary>
/// Gets the affected invite.
/// </summary>
public DiscordInvite Target { get; internal set; }
/// <summary>
/// Gets the description of invite's max age change.
/// </summary>
public PropertyChange<int?> MaxAgeChange { get; internal set; }
/// <summary>
/// Gets the description of invite's code change.
/// </summary>
public PropertyChange<string> CodeChange { get; internal set; }
/// <summary>
/// Gets the description of invite's temporariness change.
/// </summary>
public PropertyChange<bool?> TemporaryChange { get; internal set; }
/// <summary>
/// Gets the description of invite's inviting member change.
/// </summary>
public PropertyChange<DiscordMember> InviterChange { get; internal set; }
/// <summary>
/// Gets the description of invite's target channel change.
/// </summary>
public PropertyChange<DiscordChannel> ChannelChange { get; internal set; }
/// <summary>
/// Gets the description of invite's use count change.
/// </summary>
public PropertyChange<int?> UsesChange { get; internal set; }
/// <summary>
/// Gets the description of invite's max use count change.
/// </summary>
public PropertyChange<int?> MaxUsesChange { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordAuditLogInviteEntry"/> class.
/// </summary>
internal DiscordAuditLogInviteEntry() { }
}
/// <summary>
/// Represents a audit log webhook entry.
/// </summary>
public sealed class DiscordAuditLogWebhookEntry : DiscordAuditLogEntry
{
/// <summary>
/// Gets the affected webhook.
/// </summary>
public DiscordWebhook Target { get; internal set; }
/// <summary>
/// Undocumented.
/// </summary>
public PropertyChange<ulong?> IdChange { get; internal set; }
/// <summary>
/// Gets the description of webhook's name change.
/// </summary>
public PropertyChange<string> NameChange { get; internal set; }
/// <summary>
/// Gets the description of webhook's target channel change.
/// </summary>
public PropertyChange<DiscordChannel> ChannelChange { get; internal set; }
/// <summary>
/// Gets the description of webhook's type change.
/// </summary>
public PropertyChange<int?> TypeChange { get; internal set; }
/// <summary>
/// Gets the description of webhook's avatar change.
/// </summary>
public PropertyChange<string> AvatarHashChange { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordAuditLogWebhookEntry"/> class.
/// </summary>
internal DiscordAuditLogWebhookEntry() { }
}
/// <summary>
/// Represents a audit log emoji entry.
/// </summary>
public sealed class DiscordAuditLogEmojiEntry : DiscordAuditLogEntry
{
/// <summary>
/// Gets the affected emoji.
/// </summary>
public DiscordEmoji Target { get; internal set; }
/// <summary>
/// Gets the description of emoji's name change.
/// </summary>
public PropertyChange<string> NameChange { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordAuditLogEmojiEntry"/> class.
/// </summary>
internal DiscordAuditLogEmojiEntry() { }
}
/// <summary>
/// Represents a audit log sticker entry.
/// </summary>
public sealed class DiscordAuditLogStickerEntry : DiscordAuditLogEntry
{
/// <summary>
/// Gets the affected sticker.
/// </summary>
public DiscordSticker Target { get; internal set; }
/// <summary>
/// Gets the description of sticker's name change.
/// </summary>
public PropertyChange<string> NameChange { get; internal set; }
/// <summary>
/// Gets the description of sticker's description change.
/// </summary>
public PropertyChange<string> DescriptionChange { get; internal set; }
/// <summary>
/// Gets the description of sticker's tags change.
/// </summary>
public PropertyChange<string> TagsChange { get; internal set; }
/// <summary>
/// Gets the description of sticker's tags change.
/// </summary>
public PropertyChange<string> AssetChange { get; internal set; }
/// <summary>
/// Gets the description of sticker's guild id change.
/// </summary>
public PropertyChange<ulong?> GuildIdChange { get; internal set; }
/// <summary>
/// Gets the description of sticker's availability change.
/// </summary>
public PropertyChange<bool?> AvailabilityChange { get; internal set; }
/// <summary>
/// Gets the description of sticker's id change.
/// </summary>
public PropertyChange<ulong?> IdChange { get; internal set; }
/// <summary>
/// Gets the description of sticker's type change.
/// </summary>
public PropertyChange<StickerType?> TypeChange { get; internal set; }
/// <summary>
/// Gets the description of sticker's format change.
/// </summary>
public PropertyChange<StickerFormat?> FormatChange { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordAuditLogStickerEntry"/> class.
/// </summary>
internal DiscordAuditLogStickerEntry() { }
}
/// <summary>
/// Represents a audit log message entry.
/// </summary>
public sealed class DiscordAuditLogMessageEntry : DiscordAuditLogEntry
{
/// <summary>
/// Gets the affected message. Note that more often than not, this will only have ID specified.
/// </summary>
public DiscordMessage Target { get; internal set; }
/// <summary>
/// Gets the channel in which the action occurred.
/// </summary>
public DiscordChannel Channel { get; internal set; }
/// <summary>
/// Gets the number of messages that were affected.
/// </summary>
public int? MessageCount { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordAuditLogMessageEntry"/> class.
/// </summary>
internal DiscordAuditLogMessageEntry() { }
}
/// <summary>
/// Represents a audit log message pin entry.
/// </summary>
public sealed class DiscordAuditLogMessagePinEntry : DiscordAuditLogEntry
{
/// <summary>
/// Gets the affected message's user.
/// </summary>
public DiscordUser Target { get; internal set; }
/// <summary>
/// Gets the channel the message is in.
/// </summary>
public DiscordChannel Channel { get; internal set; }
/// <summary>
/// Gets the message the pin action was for.
/// </summary>
public DiscordMessage Message { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordAuditLogMessagePinEntry"/> class.
/// </summary>
internal DiscordAuditLogMessagePinEntry() { }
}
/// <summary>
/// Represents a audit log bot add entry.
/// </summary>
public sealed class DiscordAuditLogBotAddEntry : DiscordAuditLogEntry
{
/// <summary>
/// Gets the bot that has been added to the guild.
/// </summary>
public DiscordUser TargetBot { get; internal set; }
}
/// <summary>
/// Represents a audit log member move entry.
/// </summary>
public sealed class DiscordAuditLogMemberMoveEntry : DiscordAuditLogEntry
{
/// <summary>
/// Gets the channel the members were moved in.
/// </summary>
public DiscordChannel Channel { get; internal set; }
/// <summary>
/// Gets the amount of users that were moved out from the voice channel.
/// </summary>
public int UserCount { get; internal set; }
}
/// <summary>
/// Represents a audit log member disconnect entry.
/// </summary>
public sealed class DiscordAuditLogMemberDisconnectEntry : DiscordAuditLogEntry
{
/// <summary>
/// Gets the amount of users that were disconnected from the voice channel.
/// </summary>
public int UserCount { get; internal set; }
}
/// <summary>
/// Represents a audit log integration entry.
/// </summary>
public sealed class DiscordAuditLogIntegrationEntry : DiscordAuditLogEntry
{
/// <summary>
/// The type of integration.
/// </summary>
public PropertyChange<string> Type { get; internal set; }
/// <summary>
/// Gets the description of emoticons' change.
/// </summary>
public PropertyChange<bool?> EnableEmoticons { get; internal set; }
/// <summary>
/// Gets the description of expire grace period's change.
/// </summary>
public PropertyChange<int?> ExpireGracePeriod { get; internal set; }
/// <summary>
/// Gets the description of expire behavior change.
/// </summary>
public PropertyChange<int?> ExpireBehavior { get; internal set; }
}
/// <summary>
/// Represents a audit log stage entry.
/// </summary>
public sealed class DiscordAuditLogStageEntry : DiscordAuditLogEntry
{
/// <summary>
/// Gets the affected stage instance
/// </summary>
public DiscordStageInstance Target { get; internal set; }
/// <summary>
/// Gets the description of stage instance's topic change.
/// </summary>
public PropertyChange<string> TopicChange { get; internal set; }
/// <summary>
/// Gets the description of stage instance's privacy level change.
/// </summary>
#pragma warning disable CS0612 // Type or member is obsolete
public PropertyChange<StagePrivacyLevel?> PrivacyLevelChange { get; internal set; }
#pragma warning restore CS0612 // Type or member is obsolete
/// <summary>
/// Initializes a new instance of the <see cref="DiscordAuditLogStageEntry"/> class.
/// </summary>
internal DiscordAuditLogStageEntry() { }
}
/// <summary>
/// Represents a audit log event entry.
/// </summary>
public sealed class DiscordAuditLogGuildScheduledEventEntry : DiscordAuditLogEntry
{
/// <summary>
/// Gets the affected event
/// </summary>
public DiscordScheduledEvent Target { get; internal set; }
/// <summary>
/// Gets the channel change.
/// </summary>
public PropertyChange<ulong?> ChannelIdChange { get; internal set; }
/// <summary>
/// <see cref="DiscordScheduledEvent.Name"/>
/// </summary>
public PropertyChange<string> NameChange { get; internal set; }
/// <summary>
/// Gets the description change.
/// </summary>
public PropertyChange<string> DescriptionChange { get; internal set; }
/* Will be added https://github.com/discord/discord-api-docs/pull/3586#issuecomment-969137241
public PropertyChange<> ScheduledStartTimeChange { get; internal set; }
public PropertyChange<> ScheduledEndTimeChange { get; internal set; }
*/
/// <summary>
/// Gets the location change.
/// </summary>
public PropertyChange<string> LocationChange { get; internal set; }
/// <summary>
/// Gets the privacy level change.
/// </summary>
public PropertyChange<ScheduledEventPrivacyLevel?> PrivacyLevelChange { get; internal set; }
/// <summary>
/// Gets the status change.
/// </summary>
public PropertyChange<ScheduledEventStatus?> StatusChange { get; internal set; }
/// <summary>
/// Gets the entity type change.
/// </summary>
public PropertyChange<ScheduledEventEntityType?> EntityTypeChange { get; internal set; }
/*/// <summary>
/// Gets the sku ids change.
/// </summary>
public PropertyChange<IReadOnlyList<ulong>> SkuIdsChange { get; internal set; }*/
/// <summary>
/// Initializes a new instance of the <see cref="DiscordAuditLogGuildScheduledEventEntry"/> class.
/// </summary>
internal DiscordAuditLogGuildScheduledEventEntry() { }
}
/// <summary>
/// Represents a audit log thread entry.
/// </summary>
public sealed class DiscordAuditLogThreadEntry : DiscordAuditLogEntry
{
/// <summary>
/// Gets the affected thread
/// </summary>
public DiscordThreadChannel Target { get; internal set; }
/// <summary>
/// Gets the name of the thread.
/// </summary>
public PropertyChange<string> NameChange { get; internal set; }
/// <summary>
/// Gets the type of the thread.
/// </summary>
public PropertyChange<ChannelType?> TypeChange { get; internal set; }
/// <summary>
/// Gets the archived state of the thread.
/// </summary>
public PropertyChange<bool?> ArchivedChange { get; internal set; }
/// <summary>
/// Gets the locked state of the thread.
/// </summary>
public PropertyChange<bool?> LockedChange { get; internal set; }
/// <summary>
/// Gets the invitable state of the thread.
/// </summary>
public PropertyChange<bool?> InvitableChange { get; internal set; }
/// <summary>
/// Gets the new auto archive duration of the thread.
/// </summary>
public PropertyChange<ThreadAutoArchiveDuration?> AutoArchiveDurationChange { get; internal set; }
/// <summary>
/// Gets the new ratelimit of the thread.
/// </summary>
public PropertyChange<int?> PerUserRateLimitChange { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordAuditLogThreadEntry"/> class.
/// </summary>
internal DiscordAuditLogThreadEntry() { }
}
diff --git a/DisCatSharp/Entities/Guild/DiscordBan.cs b/DisCatSharp/Entities/Guild/DiscordBan.cs
index fac19dfae..4bbbea34e 100644
--- a/DisCatSharp/Entities/Guild/DiscordBan.cs
+++ b/DisCatSharp/Entities/Guild/DiscordBan.cs
@@ -1,57 +1,57 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net.Abstractions;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a Discord ban
/// </summary>
public class DiscordBan
{
/// <summary>
/// Gets the reason for the ban
/// </summary>
[JsonProperty("reason", NullValueHandling = NullValueHandling.Ignore)]
public string Reason { get; internal set; }
/// <summary>
/// Gets the banned user
/// </summary>
[JsonIgnore]
public DiscordUser User { get; internal set; }
/// <summary>
/// Gets the raw user.
/// </summary>
[JsonProperty("user", NullValueHandling = NullValueHandling.Ignore)]
internal TransportUser RawUser { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordBan"/> class.
/// </summary>
internal DiscordBan()
{ }
}
diff --git a/DisCatSharp/Entities/Guild/DiscordGuild.AuditLog.cs b/DisCatSharp/Entities/Guild/DiscordGuild.AuditLog.cs
index 2a50e9c8f..dbc06d5d3 100644
--- a/DisCatSharp/Entities/Guild/DiscordGuild.AuditLog.cs
+++ b/DisCatSharp/Entities/Guild/DiscordGuild.AuditLog.cs
@@ -1,1351 +1,1351 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Exceptions;
using DisCatSharp.Net;
using DisCatSharp.Net.Abstractions;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
namespace DisCatSharp.Entities;
public partial class DiscordGuild
{
// TODO: Rework audit logs!
/// <summary>
/// Gets audit log entries for this guild.
/// </summary>
/// <param name="limit">Maximum number of entries to fetch.</param>
/// <param name="byMember">Filter by member responsible.</param>
/// <param name="actionType">Filter by action type.</param>
/// <returns>A collection of requested audit log entries.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ViewAuditLog"/> permission.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<IReadOnlyList<DiscordAuditLogEntry>> GetAuditLogsAsync(int? limit = null, DiscordMember byMember = null, AuditLogActionType? actionType = null)
{
var alrs = new List<AuditLog>();
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 : last, byMember?.Id, (int?)actionType).ConfigureAwait(false);
ac = alr.Entries.Count;
tc += ac;
if (ac > 0)
{
last = alr.Entries[alr.Entries.Count - 1].Id;
alrs.Add(alr);
}
}
var auditLogResult = await this.ProcessAuditLog(alrs);
return auditLogResult;
}
/// <summary>
/// Proceesses audit log objects.
/// </summary>
/// <param name="auditLogApiResult">A list of raw audit log objects.</param>
/// <returns>The processed audit log list as readonly.</returns>
internal async Task<IReadOnlyList<DiscordAuditLogEntry>> ProcessAuditLog(List<AuditLog> auditLogApiResult)
{
List<AuditLogUser> amr = new();
if (auditLogApiResult.Any(ar => ar.Users != null && ar.Users.Any()))
amr = auditLogApiResult.SelectMany(xa => xa.Users)
.GroupBy(xu => xu.Id)
.Select(xgu => xgu.First()).ToList();
if (amr.Any())
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;
});
}
List<AuditLogGuildScheduledEvent> atgse = new();
if (auditLogApiResult.Any(ar => ar.ScheduledEvents != null && ar.ScheduledEvents.Any()))
atgse = auditLogApiResult.SelectMany(xa => xa.ScheduledEvents)
.GroupBy(xse => xse.Id)
.Select(xgse => xgse.First()).ToList();
List<AuditLogThread> ath = new();
if (auditLogApiResult.Any(ar => ar.Threads != null && ar.Threads.Any()))
ath = auditLogApiResult.SelectMany(xa => xa.Threads)
.GroupBy(xt => xt.Id)
.Select(xgt => xgt.First()).ToList();
List<AuditLogIntegration> aig = new();
if (auditLogApiResult.Any(ar => ar.Integrations != null && ar.Integrations.Any()))
aig = auditLogApiResult.SelectMany(xa => xa.Integrations)
.GroupBy(xi => xi.Id)
.Select(xgi => xgi.First()).ToList();
List<AuditLogWebhook> ahr = new();
if (auditLogApiResult.Any(ar => ar.Webhooks != null && ar.Webhooks.Any()))
ahr = auditLogApiResult.SelectMany(xa => xa.Webhooks)
.GroupBy(xh => xh.Id)
.Select(xgh => xgh.First()).ToList();
List<DiscordMember> ams = new();
Dictionary<ulong, DiscordMember> amd = new();
if (amr.Any())
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 }).ToList();
if (ams.Any())
amd = ams.ToDictionary(xm => xm.Id, xm => xm);
#pragma warning disable CS0219
Dictionary<ulong, DiscordThreadChannel> dtc = null;
Dictionary<ulong, DiscordIntegration> di = null;
Dictionary<ulong, DiscordScheduledEvent> dse = null;
#pragma warning restore
Dictionary<ulong, DiscordWebhook> 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 = auditLogApiResult.SelectMany(xa => xa.Entries).OrderByDescending(xa => xa.Id);
var entries = new List<DiscordAuditLogEntry>();
foreach (var xac in acs)
{
DiscordAuditLogEntry entry = null;
ulong t1, t2;
int t3, t4;
long t5, t6;
bool p1, p2;
switch (xac.ActionType)
{
case AuditLogActionType.Invalid:
break;
case AuditLogActionType.GuildUpdate:
entry = new DiscordAuditLogGuildEntry
{
Target = this
};
var entrygld = entry as DiscordAuditLogGuildEntry;
foreach (var xc in xac.Changes)
{
PropertyChange<DiscordChannel> GetChannelChange()
{
ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1);
ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2);
return new PropertyChange<DiscordChannel>
{
Before = this.GetChannel(t1) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id },
After = this.GetChannel(t2) ?? new DiscordChannel { Id = t1, Discord = this.Discord, GuildId = this.Id }
};
}
switch (xc.Key.ToLowerInvariant())
{
case "name":
entrygld.NameChange = new PropertyChange<string>
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "owner_id":
entrygld.OwnerChange = new PropertyChange<DiscordMember>
{
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<string>
{
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<VerificationLevel>
{
Before = (VerificationLevel)(long)xc.OldValue,
After = (VerificationLevel)(long)xc.NewValue
};
break;
case "afk_channel_id":
entrygld.AfkChannelChange = GetChannelChange();
break;
case "system_channel_flags":
entrygld.SystemChannelFlagsChange = new PropertyChange<SystemChannelFlags>()
{
Before = (SystemChannelFlags)(long)xc.OldValue,
After = (SystemChannelFlags)(long)xc.NewValue
};
break;
case "widget_channel_id":
entrygld.WidgetChannelChange = GetChannelChange();
break;
case "rules_channel_id":
entrygld.RulesChannelChange = GetChannelChange();
break;
case "public_updates_channel_id":
entrygld.PublicUpdatesChannelChange = GetChannelChange();
break;
case "splash_hash":
entrygld.SplashChange = new PropertyChange<string>
{
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<DefaultMessageNotifications>
{
Before = (DefaultMessageNotifications)(long)xc.OldValue,
After = (DefaultMessageNotifications)(long)xc.NewValue
};
break;
case "system_channel_id":
entrygld.SystemChannelChange = GetChannelChange();
break;
case "explicit_content_filter":
entrygld.ExplicitContentFilterChange = new PropertyChange<ExplicitContentFilter>
{
Before = (ExplicitContentFilter)(long)xc.OldValue,
After = (ExplicitContentFilter)(long)xc.NewValue
};
break;
case "mfa_level":
entrygld.MfaLevelChange = new PropertyChange<MfaLevel>
{
Before = (MfaLevel)(long)xc.OldValue,
After = (MfaLevel)(long)xc.NewValue
};
break;
case "region":
entrygld.RegionChange = new PropertyChange<string>
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "vanity_url_code":
entrygld.VanityUrlCodeChange = new PropertyChange<string>
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "premium_progress_bar_enabled":
entrygld.PremiumProgressBarChange = new PropertyChange<bool>
{
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<string>
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "type":
p1 = ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1);
p2 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2);
entrychn.TypeChange = new PropertyChange<ChannelType?>
{
Before = p1 ? (ChannelType?)t1 : null,
After = p2 ? (ChannelType?)t2 : null
};
break;
case "flags":
entrychn.ChannelFlagsChange = new PropertyChange<ChannelFlags>()
{
Before = (ChannelFlags)(long)(xc.OldValue ?? 0L),
After = (ChannelFlags)(long)(xc.NewValue ?? 0L)
};
break;
case "permission_overwrites":
var olds = xc.OldValues?.OfType<JObject>()
?.Select(xjo => xjo.ToObject<DiscordOverwrite>())
?.Select(xo => { xo.Discord = this.Discord; return xo; });
var news = xc.NewValues?.OfType<JObject>()
?.Select(xjo => xjo.ToObject<DiscordOverwrite>())
?.Select(xo => { xo.Discord = this.Discord; return xo; });
entrychn.OverwriteChange = new PropertyChange<IReadOnlyList<DiscordOverwrite>>
{
Before = olds != null ? new ReadOnlyCollection<DiscordOverwrite>(new List<DiscordOverwrite>(olds)) : null,
After = news != null ? new ReadOnlyCollection<DiscordOverwrite>(new List<DiscordOverwrite>(news)) : null
};
break;
case "topic":
entrychn.TopicChange = new PropertyChange<string>
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "nsfw":
entrychn.NsfwChange = new PropertyChange<bool?>
{
Before = (bool?)xc.OldValue,
After = (bool?)xc.NewValue
};
break;
case "rtc_region":
entrychn.RtcRegionIdChange = new PropertyChange<string>
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "bitrate":
entrychn.BitrateChange = new PropertyChange<int?>
{
Before = (int?)(long?)xc.OldValue,
After = (int?)(long?)xc.NewValue
};
break;
case "user_limit":
entrychn.UserLimitChange = new PropertyChange<int?>
{
Before = (int?)(long?)xc.OldValue,
After = (int?)(long?)xc.NewValue
};
break;
case "rate_limit_per_user":
entrychn.PerUserRateLimitChange = new PropertyChange<int?>
{
Before = (int?)(long?)xc.OldValue,
After = (int?)(long?)xc.NewValue
};
break;
case "default_auto_archive_duration":
entrychn.DefaultAutoArchiveDurationChange = new PropertyChange<ThreadAutoArchiveDuration?>
{
Before = (ThreadAutoArchiveDuration?)(long?)xc.OldValue,
After = (ThreadAutoArchiveDuration?)(long?)xc.NewValue
};
break;
case "available_tags":
var old_tags = xc.OldValues?.OfType<JObject>()
?.Select(xjo => xjo.ToObject<ForumPostTag>())
?.Select(xo => { xo.Discord = this.Discord; return xo; });
var new_tags = xc.NewValues?.OfType<JObject>()
?.Select(xjo => xjo.ToObject<ForumPostTag>())
?.Select(xo => { xo.Discord = this.Discord; return xo; });
entrychn.AvailableTagsChange = new PropertyChange<List<ForumPostTag>>
{
Before = old_tags != null ? new List<ForumPostTag>(new List<ForumPostTag>(old_tags)) : null,
After = new_tags != null ? new List<ForumPostTag>(new List<ForumPostTag>(new_tags)) : null
};
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<Permissions?>
{
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<Permissions?>
{
Before = p1 ? (Permissions?)t1 : null,
After = p2 ? (Permissions?)t2 : null
};
break;
case "type":
entryovr.TypeChange = new PropertyChange<OverwriteType?>
{
Before = xc.OldValue != null ? (OverwriteType)(long)xc.OldValue : null,
After = xc.NewValue != null ? (OverwriteType)(long)xc.NewValue : null
};
break;
case "id":
p1 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1);
p2 = ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2);
entryovr.TargetIdChange = new PropertyChange<ulong?>
{
Before = p1 ? t1 : null,
After = p2 ? t2 : null
};
break;
default:
this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in overwrite update: {0} - this should be reported to library developers", xc.Key);
break;
}
}
break;
case AuditLogActionType.Kick:
entry = new DiscordAuditLogKickEntry
{
Target = amd.TryGetValue(xac.TargetId.Value, out var kickMember) ? kickMember : new DiscordMember { Id = xac.TargetId.Value, Discord = this.Discord, GuildId = this.Id }
};
break;
case AuditLogActionType.Prune:
entry = new DiscordAuditLogPruneEntry
{
Days = xac.Options.DeleteMemberDays,
Toll = xac.Options.MembersRemoved
};
break;
case AuditLogActionType.Ban:
case AuditLogActionType.Unban:
entry = new DiscordAuditLogBanEntry
{
Target = amd.TryGetValue(xac.TargetId.Value, out var unbanMember) ? unbanMember : new DiscordMember { Id = xac.TargetId.Value, Discord = this.Discord, GuildId = this.Id }
};
break;
case AuditLogActionType.MemberUpdate:
case AuditLogActionType.MemberRoleUpdate:
entry = new DiscordAuditLogMemberUpdateEntry
{
Target = amd.TryGetValue(xac.TargetId.Value, out var roleUpdMember) ? roleUpdMember : new DiscordMember { Id = xac.TargetId.Value, Discord = this.Discord, GuildId = this.Id }
};
var entrymbu = entry as DiscordAuditLogMemberUpdateEntry;
foreach (var xc in xac.Changes)
{
switch (xc.Key.ToLowerInvariant())
{
case "nick":
entrymbu.NicknameChange = new PropertyChange<string>
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "deaf":
entrymbu.DeafenChange = new PropertyChange<bool?>
{
Before = (bool?)xc.OldValue,
After = (bool?)xc.NewValue
};
break;
case "mute":
entrymbu.MuteChange = new PropertyChange<bool?>
{
Before = (bool?)xc.OldValue,
After = (bool?)xc.NewValue
};
break;
case "communication_disabled_until":
entrymbu.CommunicationDisabledUntilChange = new PropertyChange<DateTime?>
{
Before = (DateTime?)xc.OldValue,
After = (DateTime?)xc.NewValue
};
break;
case "$add":
entrymbu.AddedRoles = new ReadOnlyCollection<DiscordRole>(xc.NewValues.Select(xo => (ulong)xo["id"]).Select(this.GetRole).ToList());
break;
case "$remove":
entrymbu.RemovedRoles = new ReadOnlyCollection<DiscordRole>(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<string>
{
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<int?>
{
Before = p1 ? t3 : null,
After = p2 ? t4 : null
};
break;
case "permissions":
entryrol.PermissionChange = new PropertyChange<Permissions?>
{
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<int?>
{
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<bool?>
{
Before = xc.OldValue != null ? (bool?)xc.OldValue : null,
After = xc.NewValue != null ? (bool?)xc.NewValue : null
};
break;
case "hoist":
entryrol.HoistChange = new PropertyChange<bool?>
{
Before = (bool?)xc.OldValue,
After = (bool?)xc.NewValue
};
break;
case "icon_hash":
entryrol.IconHashChange = new PropertyChange<string>
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
default:
this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in role update: {0} - this should be reported to library developers", xc.Key);
break;
}
}
break;
case AuditLogActionType.InviteCreate:
case AuditLogActionType.InviteDelete:
case AuditLogActionType.InviteUpdate:
entry = new DiscordAuditLogInviteEntry();
var inv = new DiscordInvite
{
Discord = this.Discord,
Guild = new DiscordInviteGuild
{
Discord = this.Discord,
Id = this.Id,
Name = this.Name,
SplashHash = this.SplashHash
}
};
var entryinv = entry as DiscordAuditLogInviteEntry;
foreach (var xc in xac.Changes)
{
switch (xc.Key.ToLowerInvariant())
{
case "max_age":
p1 = int.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t3);
p2 = int.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t4);
entryinv.MaxAgeChange = new PropertyChange<int?>
{
Before = p1 ? t3 : null,
After = p2 ? t4 : null
};
break;
case "code":
inv.Code = xc.OldValueString ?? xc.NewValueString;
entryinv.CodeChange = new PropertyChange<string>
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "temporary":
entryinv.TemporaryChange = new PropertyChange<bool?>
{
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<DiscordMember>
{
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<DiscordChannel>
{
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<int?>
{
Before = p1 ? t3 : null,
After = p2 ? 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<int?>
{
Before = p1 ? t3 : null,
After = p2 ? t4 : null
};
break;
// TODO: Add changes for target application
default:
this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in invite update: {0} - this should be reported to library developers", xc.Key);
break;
}
}
entryinv.Target = inv;
break;
case AuditLogActionType.WebhookCreate:
case AuditLogActionType.WebhookDelete:
case AuditLogActionType.WebhookUpdate:
entry = new DiscordAuditLogWebhookEntry
{
Target = ahd.TryGetValue(xac.TargetId.Value, out var webhook) ? webhook : new DiscordWebhook { Id = xac.TargetId.Value, Discord = this.Discord }
};
var entrywhk = entry as DiscordAuditLogWebhookEntry;
foreach (var xc in xac.Changes)
{
switch (xc.Key.ToLowerInvariant())
{
case "application_id": // ???
p1 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1);
p2 = ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2);
entrywhk.IdChange = new PropertyChange<ulong?>
{
Before = p1 ? t1 : null,
After = p2 ? t2 : null
};
break;
case "name":
entrywhk.NameChange = new PropertyChange<string>
{
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<DiscordChannel>
{
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<int?>
{
Before = p1 ? t3 : null,
After = p2 ? t4 : null
};
break;
case "avatar_hash":
entrywhk.AvatarHashChange = new PropertyChange<string>
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
default:
this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in webhook update: {0} - this should be reported to library developers", xc.Key);
break;
}
}
break;
case AuditLogActionType.EmojiCreate:
case AuditLogActionType.EmojiDelete:
case AuditLogActionType.EmojiUpdate:
entry = new DiscordAuditLogEmojiEntry
{
Target = this.EmojisInternal.TryGetValue(xac.TargetId.Value, out var target) ? target : new DiscordEmoji { Id = xac.TargetId.Value, Discord = this.Discord }
};
var entryemo = entry as DiscordAuditLogEmojiEntry;
foreach (var xc in xac.Changes)
{
switch (xc.Key.ToLowerInvariant())
{
case "name":
entryemo.NameChange = new PropertyChange<string>
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
default:
this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in emote update: {0} - this should be reported to library developers", xc.Key);
break;
}
}
break;
case AuditLogActionType.StageInstanceCreate:
case AuditLogActionType.StageInstanceDelete:
case AuditLogActionType.StageInstanceUpdate:
entry = new DiscordAuditLogStageEntry
{
Target = this.StageInstancesInternal.TryGetValue(xac.TargetId.Value, out var stage) ? stage : new DiscordStageInstance { Id = xac.TargetId.Value, Discord = this.Discord }
};
var entrysta = entry as DiscordAuditLogStageEntry;
foreach (var xc in xac.Changes)
{
switch (xc.Key.ToLowerInvariant())
{
case "topic":
entrysta.TopicChange = new PropertyChange<string>
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "privacy_level":
#pragma warning disable CS0612 // Type or member is obsolete
entrysta.PrivacyLevelChange = new PropertyChange<StagePrivacyLevel?>
{
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,
};
#pragma warning restore CS0612 // Type or member is obsolete
break;
default:
this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in stage instance update: {0} - this should be reported to library developers", xc.Key);
break;
}
}
break;
case AuditLogActionType.StickerCreate:
case AuditLogActionType.StickerDelete:
case AuditLogActionType.StickerUpdate:
entry = new DiscordAuditLogStickerEntry
{
Target = this.StickersInternal.TryGetValue(xac.TargetId.Value, out var sticker) ? sticker : new DiscordSticker { Id = xac.TargetId.Value, Discord = this.Discord }
};
var entrysti = entry as DiscordAuditLogStickerEntry;
foreach (var xc in xac.Changes)
{
switch (xc.Key.ToLowerInvariant())
{
case "name":
entrysti.NameChange = new PropertyChange<string>
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "description":
entrysti.DescriptionChange = new PropertyChange<string>
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "tags":
entrysti.TagsChange = new PropertyChange<string>
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "guild_id":
entrysti.GuildIdChange = new PropertyChange<ulong?>
{
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<bool?>
{
Before = (bool?)xc.OldValue,
After = (bool?)xc.NewValue,
};
break;
case "asset":
entrysti.AssetChange = new PropertyChange<string>
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "id":
entrysti.IdChange = new PropertyChange<ulong?>
{
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<StickerType?>
{
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<StickerFormat?>
{
Before = p1 ? (StickerFormat?)t5 : null,
After = p2 ? (StickerFormat?)t6 : null
};
break;
default:
this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in sticker update: {0} - this should be reported to library developers", xc.Key);
break;
}
}
break;
case AuditLogActionType.MessageDelete:
case AuditLogActionType.MessageBulkDelete:
{
entry = new DiscordAuditLogMessageEntry();
var entrymsg = entry as DiscordAuditLogMessageEntry;
if (xac.Options != null)
{
entrymsg.Channel = this.GetChannel(xac.Options.ChannelId) ?? new DiscordChannel { Id = xac.Options.ChannelId, Discord = this.Discord, GuildId = this.Id };
entrymsg.MessageCount = xac.Options.Count;
}
if (entrymsg.Channel != null)
{
entrymsg.Target = this.Discord is DiscordClient dc
&& dc.MessageCache != null
&& dc.MessageCache.TryGet(xm => xm.Id == xac.TargetId.Value && xm.ChannelId == entrymsg.Channel.Id, out var msg)
? msg
: new DiscordMessage { Discord = this.Discord, Id = xac.TargetId.Value };
}
break;
}
case AuditLogActionType.MessagePin:
case AuditLogActionType.MessageUnpin:
{
entry = new DiscordAuditLogMessagePinEntry();
var entrypin = entry as DiscordAuditLogMessagePinEntry;
if (this.Discord is not DiscordClient dc)
{
break;
}
if (xac.Options != null)
{
DiscordMessage message = default;
dc.MessageCache?.TryGet(x => x.Id == xac.Options.MessageId && x.ChannelId == xac.Options.ChannelId, out message);
entrypin.Channel = this.GetChannel(xac.Options.ChannelId) ?? new DiscordChannel { Id = xac.Options.ChannelId, Discord = this.Discord, GuildId = this.Id };
entrypin.Message = message ?? new DiscordMessage { Id = xac.Options.MessageId, Discord = this.Discord };
}
if (xac.TargetId.HasValue)
{
dc.UserCache.TryGetValue(xac.TargetId.Value, out var user);
entrypin.Target = user ?? new DiscordUser { Id = user.Id, Discord = this.Discord };
}
break;
}
case AuditLogActionType.BotAdd:
{
entry = new DiscordAuditLogBotAddEntry();
if (!(this.Discord is DiscordClient dc && xac.TargetId.HasValue))
{
break;
}
dc.UserCache.TryGetValue(xac.TargetId.Value, out var bot);
(entry as DiscordAuditLogBotAddEntry).TargetBot = bot ?? new DiscordUser { Id = xac.TargetId.Value, Discord = this.Discord };
break;
}
case AuditLogActionType.MemberMove:
entry = new DiscordAuditLogMemberMoveEntry();
if (xac.Options == null)
{
break;
}
var moveentry = entry as DiscordAuditLogMemberMoveEntry;
moveentry.UserCount = xac.Options.Count;
moveentry.Channel = this.GetChannel(xac.Options.ChannelId) ?? new DiscordChannel { Id = xac.Options.ChannelId, Discord = this.Discord, GuildId = this.Id };
break;
case AuditLogActionType.MemberDisconnect:
entry = new DiscordAuditLogMemberDisconnectEntry
{
UserCount = xac.Options?.Count ?? 0
};
break;
case AuditLogActionType.IntegrationCreate:
case AuditLogActionType.IntegrationDelete:
case AuditLogActionType.IntegrationUpdate:
entry = new DiscordAuditLogIntegrationEntry();
var integentry = entry as DiscordAuditLogIntegrationEntry;
foreach (var xc in xac.Changes)
{
switch (xc.Key.ToLowerInvariant())
{
case "type":
integentry.Type = new PropertyChange<string>()
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "enable_emoticons":
integentry.EnableEmoticons = new PropertyChange<bool?>
{
Before = (bool?)xc.OldValue,
After = (bool?)xc.NewValue
};
break;
case "expire_behavior":
integentry.ExpireBehavior = new PropertyChange<int?>
{
Before = (int?)xc.OldValue,
After = (int?)xc.NewValue
};
break;
case "expire_grace_period":
integentry.ExpireBehavior = new PropertyChange<int?>
{
Before = (int?)xc.OldValue,
After = (int?)xc.NewValue
};
break;
default:
this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in integration update: {0} - this should be reported to library developers", xc.Key);
break;
}
}
break;
case AuditLogActionType.ThreadCreate:
case AuditLogActionType.ThreadDelete:
case AuditLogActionType.ThreadUpdate:
entry = new DiscordAuditLogThreadEntry
{
Target = this.ThreadsInternal.TryGetValue(xac.TargetId.Value, out var thread) ? thread : new DiscordThreadChannel { Id = xac.TargetId.Value, Discord = this.Discord }
};
var entrythr = entry as DiscordAuditLogThreadEntry;
foreach (var xc in xac.Changes)
{
switch (xc.Key.ToLowerInvariant())
{
case "name":
entrythr.NameChange = new PropertyChange<string>
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "type":
p1 = ulong.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t1);
p2 = ulong.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t2);
entrythr.TypeChange = new PropertyChange<ChannelType?>
{
Before = p1 ? (ChannelType?)t1 : null,
After = p2 ? (ChannelType?)t2 : null
};
break;
case "archived":
entrythr.ArchivedChange = new PropertyChange<bool?>
{
Before = xc.OldValue != null ? (bool?)xc.OldValue : null,
After = xc.NewValue != null ? (bool?)xc.NewValue : null
};
break;
case "locked":
entrythr.LockedChange = new PropertyChange<bool?>
{
Before = xc.OldValue != null ? (bool?)xc.OldValue : null,
After = xc.NewValue != null ? (bool?)xc.NewValue : null
};
break;
case "invitable":
entrythr.InvitableChange = new PropertyChange<bool?>
{
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<ThreadAutoArchiveDuration?>
{
Before = p1 ? (ThreadAutoArchiveDuration?)t5 : null,
After = p2 ? (ThreadAutoArchiveDuration?)t6 : null
};
break;
case "rate_limit_per_user":
entrythr.PerUserRateLimitChange = new PropertyChange<int?>
{
Before = (int?)(long?)xc.OldValue,
After = (int?)(long?)xc.NewValue
};
break;
default:
this.Discord.Logger.LogWarning(LoggerEvents.AuditLog, "Unknown key in thread update: {0} - this should be reported to library developers", xc.Key);
break;
}
}
break;
case AuditLogActionType.GuildScheduledEventCreate:
case AuditLogActionType.GuildScheduledEventDelete:
case AuditLogActionType.GuildScheduledEventUpdate:
entry = new DiscordAuditLogGuildScheduledEventEntry
{
Target = this.ScheduledEventsInternal.TryGetValue(xac.TargetId.Value, out var scheduledEvent) ? scheduledEvent : new DiscordScheduledEvent { Id = xac.TargetId.Value, Discord = this.Discord }
};
var entryse = entry as DiscordAuditLogGuildScheduledEventEntry;
foreach (var xc in xac.Changes)
{
switch (xc.Key.ToLowerInvariant())
{
case "channel_id":
entryse.ChannelIdChange = new PropertyChange<ulong?>
{
Before = ulong.TryParse(xc.OldValueString, out var ogid) ? ogid : null,
After = ulong.TryParse(xc.NewValueString, out var ngid) ? ngid : null
};
break;
case "name":
entryse.NameChange = new PropertyChange<string>
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "description":
entryse.DescriptionChange = new PropertyChange<string>
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "location":
entryse.LocationChange = new PropertyChange<string>
{
Before = xc.OldValueString,
After = xc.NewValueString
};
break;
case "privacy_level":
p1 = long.TryParse(xc.OldValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t5);
p2 = long.TryParse(xc.NewValue as string, NumberStyles.Integer, CultureInfo.InvariantCulture, out t6);
entryse.PrivacyLevelChange = new PropertyChange<ScheduledEventPrivacyLevel?>
{
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<ScheduledEventEntityType?>
{
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<ScheduledEventStatus?>
{
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;
// TODO: Handle ApplicationCommandPermissionUpdate
case AuditLogActionType.ApplicationCommandPermissionUpdate:
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.Any() && amd.TryGetValue(xac.UserId, out var resp) ? resp : this.MembersInternal[xac.UserId];
entries.Add(entry);
}
return new ReadOnlyCollection<DiscordAuditLogEntry>(entries);
}
}
diff --git a/DisCatSharp/Entities/Guild/DiscordGuild.Features.cs b/DisCatSharp/Entities/Guild/DiscordGuild.Features.cs
index 2ebd593ac..fc8c97b76 100644
--- a/DisCatSharp/Entities/Guild/DiscordGuild.Features.cs
+++ b/DisCatSharp/Entities/Guild/DiscordGuild.Features.cs
@@ -1,672 +1,672 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DisCatSharp.Attributes;
using DisCatSharp.Enums;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents the guild features.
/// </summary>
public class GuildFeatures
{
/// <summary>
/// List of all guild features.
/// </summary>
public List<GuildFeaturesEnum> Features { get; }
/// <summary>
/// Checks the guild features and constructs a new <see cref="GuildFeatures"/> object.
/// </summary>
/// <param name="guild">Guild to check</param>
public GuildFeatures(DiscordGuild guild)
{
this.Features = new List<GuildFeaturesEnum>();
if (guild.RawFeatures.Contains("APPLICATION_COMMAND_PERMISSIONS_V2")) this.Features.Add(GuildFeaturesEnum.UsesApplicationCommandsPermissionsV2);
if (guild.RawFeatures.Contains("RAID_ALERTS_ENABLED")) this.Features.Add(GuildFeaturesEnum.RaidAlertsEnabled);
if (guild.RawFeatures.Contains("CREATOR_MONETIZABLE_RESTRICTED")) this.Features.Add(GuildFeaturesEnum.CreatorMonetizableRestricted);
if (guild.RawFeatures.Contains("VOICE_IN_THREADS")) this.Features.Add(GuildFeaturesEnum.VoiceInThreadsEnabled);
if (guild.RawFeatures.Contains("CHANNEL_HIGHLIGHTS_DISABLED")) this.Features.Add(GuildFeaturesEnum.ChannelHighlightsDisabled);
if (guild.RawFeatures.Contains("CHANNEL_HIGHLIGHTS")) this.Features.Add(GuildFeaturesEnum.ChannelHighlights);
if (guild.RawFeatures.Contains("GUILD_ONBOARDING_EVER_ENABLED")) this.Features.Add(GuildFeaturesEnum.HadGuildOnBoardingEverEnabled);
if (guild.RawFeatures.Contains("BURST_REACTIONS")) this.Features.Add(GuildFeaturesEnum.CanUseBurstReactions);
if (guild.RawFeatures.Contains("CREATOR_STORE_PAGE")) this.Features.Add(GuildFeaturesEnum.CanUseCreatorStorePage);
if (guild.RawFeatures.Contains("ANIMATED_ICON")) this.Features.Add(GuildFeaturesEnum.CanSetAnimatedIcon);
if (guild.RawFeatures.Contains("ANIMATED_BANNER")) this.Features.Add(GuildFeaturesEnum.CanSetAnimatedBanner);
if (guild.RawFeatures.Contains("BANNER")) this.Features.Add(GuildFeaturesEnum.CanSetBanner);
if (guild.RawFeatures.Contains("COMMUNITY")) this.Features.Add(GuildFeaturesEnum.HasCommunityEnabled);
if (!guild.RawFeatures.Contains("DISCOVERABLE_DISABLED") && guild.RawFeatures.Contains("DISCOVERABLE")) this.Features.Add(GuildFeaturesEnum.IsDiscoverable);
if (guild.RawFeatures.Contains("FEATUREABLE")) this.Features.Add(GuildFeaturesEnum.IsFeatureable);
if (guild.RawFeatures.Contains("INVITE_SPLASH")) this.Features.Add(GuildFeaturesEnum.CanSetInviteSplash);
if (guild.RawFeatures.Contains("MEMBER_VERIFICATION_GATE_ENABLED")) this.Features.Add(GuildFeaturesEnum.HasMembershipScreeningEnabled);
if (guild.RawFeatures.Contains("NEWS")) this.Features.Add(GuildFeaturesEnum.CanCreateNewsChannels);
if (guild.RawFeatures.Contains("PARTNERED")) this.Features.Add(GuildFeaturesEnum.IsPartnered);
if (guild.RawFeatures.Contains("MORE_EMOJI")) this.Features.Add(GuildFeaturesEnum.CanUploadMoreEmojis);
if (guild.RawFeatures.Contains("PREVIEW_ENABLED")) this.Features.Add(GuildFeaturesEnum.HasPreviewEnabled);
if (guild.RawFeatures.Contains("VANITY_URL")) this.Features.Add(GuildFeaturesEnum.CanSetVanityUrl);
if (guild.RawFeatures.Contains("VERIFIED")) this.Features.Add(GuildFeaturesEnum.IsVerified);
if (guild.RawFeatures.Contains("VIP_REGIONS")) this.Features.Add(GuildFeaturesEnum.CanAccessVipRegions);
if (guild.RawFeatures.Contains("WELCOME_SCREEN_ENABLED")) this.Features.Add(GuildFeaturesEnum.HasWelcomeScreenEnabled);
if (guild.RawFeatures.Contains("TICKETED_EVENTS_ENABLED")) this.Features.Add(GuildFeaturesEnum.HasTicketedEventsEnabled);
if (guild.RawFeatures.Contains("MONETIZATION_ENABLED")) this.Features.Add(GuildFeaturesEnum.HasMonetizationEnabled);
if (guild.RawFeatures.Contains("MORE_STICKERS")) this.Features.Add(GuildFeaturesEnum.CanUploadMoreStickers);
if (guild.RawFeatures.Contains("HUB")) this.Features.Add(GuildFeaturesEnum.IsHub);
if (guild.RawFeatures.Contains("THREADS_ENABLED_TESTING")) this.Features.Add(GuildFeaturesEnum.HasThreadTestingEnabled);
if (guild.RawFeatures.Contains("THREADS_ENABLED")) this.Features.Add(GuildFeaturesEnum.HasThreadsEnabled);
if (guild.RawFeatures.Contains("ROLE_ICONS")) this.Features.Add(GuildFeaturesEnum.CanSetRoleIcons);
if (guild.RawFeatures.Contains("NEW_THREAD_PERMISSIONS")) this.Features.Add(GuildFeaturesEnum.HasNewThreadPermissions);
if (guild.RawFeatures.Contains("ROLE_SUBSCRIPTIONS_ENABLED")) this.Features.Add(GuildFeaturesEnum.HasRoleSubscriptionsEnabled);
if (guild.RawFeatures.Contains("PREMIUM_TIER_3_OVERRIDE")) this.Features.Add(GuildFeaturesEnum.PremiumTierThreeOverride);
if (guild.RawFeatures.Contains("THREAD_DEFAULT_AUTO_ARCHIVE_DURATION")) this.Features.Add(GuildFeaturesEnum.CanSetThreadDefaultAutoArchiveDuration);
if (guild.RawFeatures.Contains("TEXT_IN_VOICE_ENABLED")) this.Features.Add(GuildFeaturesEnum.TextInVoiceEnabled);
if (guild.RawFeatures.Contains("HAS_DIRECTORY_ENTRY")) this.Features.Add(GuildFeaturesEnum.HasDirectoryEntry);
if (guild.RawFeatures.Contains("LINKED_TO_HUB")) this.Features.Add(GuildFeaturesEnum.IsLinkedToHub);
if (guild.RawFeatures.Contains("MEMBER_PROFILES")) this.Features.Add(GuildFeaturesEnum.HasMemberProfiles);
if (guild.RawFeatures.Contains("INTERNAL_EMPLOYEE_ONLY")) this.Features.Add(GuildFeaturesEnum.IsStaffOnly);
if (guild.RawFeatures.Contains("ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE")) this.Features.Add(GuildFeaturesEnum.RoleSubscriptionsIsAvailableForPurchase);
if (guild.RawFeatures.Contains("AUTO_MODERATION")) this.Features.Add(GuildFeaturesEnum.CanSetupAutoModeration);
if (guild.RawFeatures.Contains("GUILD_HOME_TEST")) this.Features.Add(GuildFeaturesEnum.GuildHomeTest);
if (guild.RawFeatures.Contains("INVITES_DISABLED")) this.Features.Add(GuildFeaturesEnum.InvitesDisabled);
if (guild.RawFeatures.Contains("ACTIVITIES_ALPHA")) this.Features.Add(GuildFeaturesEnum.ActivitiesAlpha);
if (guild.RawFeatures.Contains("ACTIVITIES_EMPLOYEE")) this.Features.Add(GuildFeaturesEnum.ActivitiesEmployee);
if (guild.RawFeatures.Contains("ACTIVITIES_INTERNAL_DEV")) this.Features.Add(GuildFeaturesEnum.ActivitiesInternalDev);
if (guild.RawFeatures.Contains("AUTOMOD_TRIGGER_KEYWORD_FILTER")) this.Features.Add(GuildFeaturesEnum.AutomodTriggerKeywordFilter);
if (guild.RawFeatures.Contains("AUTOMOD_TRIGGER_ML_SPAM_FILTER")) this.Features.Add(GuildFeaturesEnum.AutomodTriggerMlSpamFilter);
if (guild.RawFeatures.Contains("AUTOMOD_TRIGGER_SPAM_LINK_FILTERGuild")) this.Features.Add(GuildFeaturesEnum.AutomodTriggerSpamLinkFilterGuild);
if (guild.RawFeatures.Contains("AUTOMOD_DEFAULT_LIST")) this.Features.Add(GuildFeaturesEnum.AutomodDefaultList);
if (guild.RawFeatures.Contains("BFG")) this.Features.Add(GuildFeaturesEnum.Bfg);
if (guild.RawFeatures.Contains("BOOSTING_TIERS_EXPERIMENT_MEDIUM_GUILD")) this.Features.Add(GuildFeaturesEnum.BoostingTiersExperimentMediumGuild);
if (guild.RawFeatures.Contains("BOOSTING_TIERS_EXPERIMENT_SMALL_GUILD")) this.Features.Add(GuildFeaturesEnum.BoostingTiersExperimentSmallGuild);
if (guild.RawFeatures.Contains("BOT_DEVELOPER_EARLY_ACCESS")) this.Features.Add(GuildFeaturesEnum.BotDeveloperEarlyAccess);
if (guild.RawFeatures.Contains("CREATOR_MONETIZABLE")) this.Features.Add(GuildFeaturesEnum.CreatorMonetizable);
if (guild.RawFeatures.Contains("CREATOR_MONETIZABLE_DISABLED")) this.Features.Add(GuildFeaturesEnum.CreatorMonetizableDisabled);
if (guild.RawFeatures.Contains("CREATOR_MONETIZABLE_PROVISIONAL")) this.Features.Add(GuildFeaturesEnum.CreatorMonetizableProvisional);
if (guild.RawFeatures.Contains("CREATOR_MONETIZABLE_WHITEGLOVE")) this.Features.Add(GuildFeaturesEnum.CreatorMonetizableWhiteGlove);
if (guild.RawFeatures.Contains("CREATOR_MONETIZATION_APPLICATION_ALLOWLIST")) this.Features.Add(GuildFeaturesEnum.CreatorMonetizationApplicationAllowlist);
if (guild.RawFeatures.Contains("DEVELOPER_SUPPORT_SERVER")) this.Features.Add(GuildFeaturesEnum.DeveloperSupportServer);
if (guild.RawFeatures.Contains("EXPOSED_TO_ACTIVITIES_WTP_EXPERIMENT")) this.Features.Add(GuildFeaturesEnum.ExposedToActivitiesWtpExperiment);
if (guild.RawFeatures.Contains("GUILD_COMMUNICATION_DISABLED_GUILDS")) this.Features.Add(GuildFeaturesEnum.GuildCommunicationDisabledGuilds);
if (guild.RawFeatures.Contains("DISABLE_GUILD_COMMUNICATION")) this.Features.Add(GuildFeaturesEnum.DisableGuildCommunication);
if (guild.RawFeatures.Contains("GUILD_HOME_OVERRIDE")) this.Features.Add(GuildFeaturesEnum.GuildHomeOverride);
if (guild.RawFeatures.Contains("GUILD_AUTOMOD_DEFAULT_LIST")) this.Features.Add(GuildFeaturesEnum.GuildAutomodDefaultList);
if (guild.RawFeatures.Contains("GUILD_MEMBER_VERIFICATION_EXPERIMENT")) this.Features.Add(GuildFeaturesEnum.GuildMemberVerificationExperiment);
if (guild.RawFeatures.Contains("GUILD_ROLE_SUBSCRIPTION_PURCHASE_FEEDBACK_LOOP")) this.Features.Add(GuildFeaturesEnum.GuildRoleSubscriptionPurchaseFeedbackLoop);
if (guild.RawFeatures.Contains("GUILD_ROLE_SUBSCRIPTION_TRIALS")) this.Features.Add(GuildFeaturesEnum.GuildRoleSubscriptionTrials);
if (guild.RawFeatures.Contains("HAD_EARLY_ACTIVITIES_ACCESS")) this.Features.Add(GuildFeaturesEnum.HadEarlyActivitiesAccess);
if (guild.RawFeatures.Contains("INCREASED_THREAD_LIMIT")) this.Features.Add(GuildFeaturesEnum.IncreasedThreadLimit);
if (guild.RawFeatures.Contains("MOBILE_WEB_ROLE_SUBSCRIPTION_PURCHASE_PAGE")) this.Features.Add(GuildFeaturesEnum.MobileWebRoleSubscriptionPurchasePage);
if (guild.RawFeatures.Contains("RELAY_ENABLED")) this.Features.Add(GuildFeaturesEnum.RelayEnabled);
if (guild.RawFeatures.Contains("RESTRICT_SPAM_RISK_GUILDS")) this.Features.Add(GuildFeaturesEnum.RestrictSpamRiskGuilds);
if (guild.RawFeatures.Contains("ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE")) this.Features.Add(GuildFeaturesEnum.RoleSubscriptionsAvailableForPurchase);
if (guild.RawFeatures.Contains("THREADS_ENABLED_TESTING")) this.Features.Add(GuildFeaturesEnum.ThreadsEnabledTesting);
if (guild.RawFeatures.Contains("VOICE_CHANNEL_EFFECTS")) this.Features.Add(GuildFeaturesEnum.VoiceChannelEffects);
if (guild.RawFeatures.Contains("SOUNDBOARD")) this.Features.Add(GuildFeaturesEnum.Soundboard);
if (guild.RawFeatures.Contains("COMMERCE")) this.Features.Add(GuildFeaturesEnum.Commerce);
if (guild.RawFeatures.Contains("EXPOSED_TO_BOOSTING_TIERS_EXPERIMENT")) this.Features.Add(GuildFeaturesEnum.ExposedToBoostingTiersExperiment);
if (guild.RawFeatures.Contains("PUBLIC_DISABLED")) this.Features.Add(GuildFeaturesEnum.PublicDisabled);
if (guild.RawFeatures.Contains("PUBLIC")) this.Features.Add(GuildFeaturesEnum.Public);
if (guild.RawFeatures.Contains("SEVEN_DAY_THREAD_ARCHIVE")) this.Features.Add(GuildFeaturesEnum.SevenDayThreadArchive);
if (guild.RawFeatures.Contains("THREE_DAY_THREAD_ARCHIVE")) this.Features.Add(GuildFeaturesEnum.ThreeDayThreadArchive);
if (guild.RawFeatures.Contains("FEATURABLE")) this.Features.Add(GuildFeaturesEnum.Featurable);
if (guild.RawFeatures.Contains("FORCE_RELAY")) this.Features.Add(GuildFeaturesEnum.ForceRelay);
if (guild.RawFeatures.Contains("LURKABLE")) this.Features.Add(GuildFeaturesEnum.Lurkable);
if (guild.RawFeatures.Contains("MEMBER_LIST_DISABLED")) this.Features.Add(GuildFeaturesEnum.MemberListDisabled);
if (guild.RawFeatures.Contains("CHANNEL_BANNER")) this.Features.Add(GuildFeaturesEnum.CanSetChannelBanner);
if (guild.RawFeatures.Contains("PRIVATE_THREADS")) this.Features.Add(GuildFeaturesEnum.CanCreatePrivateThreads);
}
/// <summary>
/// Checks whether the guild has a feature enabled.
/// </summary>
/// <param name="flag">The feature you'd like to check for.</param>
/// <returns>Whether the guild has the requested feature.</returns>
public bool HasFeature(GuildFeaturesEnum flag)
=> this.Features.Contains(flag);
public string ToString(string separator, bool humanReadable)
{
if (!humanReadable) return string.Join(separator, this.Features);
else
{
var humanReadableFeatures = this.Features.Select(x => AddSpacesToWord(x.ToString()));
return string.Join(separator, humanReadableFeatures);
}
}
/// <summary>
/// Converts a string of characters (here: enum) into a string of characters separated by spaces after a capital letter.
/// </summary>
/// <param name="text">String of text to convert</param>
/// <returns>String separated by a space after every capital letter.</returns>
private static string AddSpacesToWord(string text)
{
if (string.IsNullOrWhiteSpace(text))
return "";
var newText = new StringBuilder(text.Length * 2);
newText.Append(text[0]);
for (var i = 1; i < text.Length; i++)
{
if (char.IsUpper(text[i]) && text[i - 1] != ' ')
newText.Append(' ');
newText.Append(text[i]);
}
return newText.ToString();
}
}
/// <summary>
/// Represents the guild features.
/// </summary>
public enum GuildFeaturesEnum
{
/// <summary>
/// Guild has access to set an animated guild icon.
/// </summary>
CanSetAnimatedIcon,
/// <summary>
/// Guild has access to set a guild banner image.
/// </summary>
CanSetBanner,
/// <summary>
/// Guild has access to use commerce features (i.e. create store channels)
/// </summary>
[Obsolete("Store applications are EOL.")]
CanCreateStoreChannels,
/// <summary>
/// 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).
/// <see cref="ChannelType.Stage"/> and <see cref="ChannelType.News"/> is usable.
/// </summary>
HasCommunityEnabled,
/// <summary>
/// Guild is able to be discovered in the discovery.
/// </summary>
IsDiscoverable,
/// <summary>
/// Guild is able to be featured in the discovery.
/// </summary>
IsFeatureable,
/// <summary>
/// Guild has access to set an invite splash background.
/// </summary>
CanSetInviteSplash,
/// <summary>
/// Guild has enabled Membership Screening.
/// </summary>
HasMembershipScreeningEnabled,
/// <summary>
/// Guild has access to create news channels.
/// <see cref="ChannelType.News"/> is usable.
/// </summary>
CanCreateNewsChannels,
/// <summary>
/// Guild is partnered.
/// </summary>
IsPartnered,
/// <summary>
/// Guild has increased custom emoji slots.
/// </summary>
CanUploadMoreEmojis,
/// <summary>
/// Guild can be previewed before joining via Membership Screening or the discovery.
/// </summary>
HasPreviewEnabled,
/// <summary>
/// Guild has access to set a vanity URL.
/// </summary>
CanSetVanityUrl,
/// <summary>
/// Guild is verified.
/// </summary>
IsVerified,
/// <summary>
/// Guild has access to set 384kbps bitrate in voice (previously VIP voice servers).
/// </summary>
CanAccessVipRegions,
/// <summary>
/// Guild has enabled the welcome screen.
/// </summary>
HasWelcomeScreenEnabled,
/// <summary>
/// Guild has enabled ticketed events.
/// </summary>
HasTicketedEventsEnabled,
/// <summary>
/// Guild has enabled monetization.
/// </summary>
HasMonetizationEnabled,
/// <summary>
/// Guild has increased custom sticker slots.
/// </summary>
CanUploadMoreStickers,
/// <summary>
/// Guild has access to the three day archive time for threads.
/// Needs Premium Tier 1 (<see cref="PremiumTier.TierOne"/>).
/// </summary>
[DiscordDeprecated("Auto archive duration isn't locked to boosts anymore."), Obsolete]
CanSetThreadArchiveDurationThreeDays,
/// <summary>
/// Guild has access to the seven day archive time for threads.
/// Needs Premium Tier 2 (<see cref="PremiumTier.TierTwo"/>).
/// </summary>
[DiscordDeprecated("Auto archive duration isn't locked to boosts anymore."), Obsolete]
CanSetThreadArchiveDurationSevenDays,
/// <summary>
/// Guild has access to create private threads.
/// Needs Premium Tier 2 (<see cref="PremiumTier.TierTwo"/>).
/// </summary>
[DiscordDeprecated("Private threads aren't bound to the server boost level anymore and can be used by everyone."), Obsolete]
CanCreatePrivateThreads,
/// <summary>
/// Guild is a hub.
/// <see cref="ChannelType.GuildDirectory"/> is usable.
/// </summary>
IsHub,
/// <summary>
/// Guild is in a hub.
/// https://github.com/discord/discord-api-docs/pull/3757/commits/4932d92c9d0c783861bc715bf7ebbabb15114e34
/// </summary>
HasDirectoryEntry,
/// <summary>
/// Guild is linked to a hub.
/// </summary>
IsLinkedToHub,
/// <summary>
/// Guild has full access to threads.
/// Old Feature.
/// </summary>
HasThreadTestingEnabled,
/// <summary>
/// Guild has access to threads.
/// </summary>
HasThreadsEnabled,
/// <summary>
/// Guild can set role icons.
/// </summary>
CanSetRoleIcons,
/// <summary>
/// Guild has the new thread permissions.
/// Old Feature.
/// </summary>
HasNewThreadPermissions,
/// <summary>
/// Guild can set thread default auto archive duration.
/// Old Feature.
/// </summary>
CanSetThreadDefaultAutoArchiveDuration,
/// <summary>
/// Guild has enabled role subscriptions.
/// </summary>
HasRoleSubscriptionsEnabled,
/// <summary>
/// Guild role subscriptions as purchaseable.
/// </summary>
RoleSubscriptionsIsAvailableForPurchase,
/// <summary>
/// Guild has premium tier 3 override.
/// </summary>
PremiumTierThreeOverride,
/// <summary>
/// Guild has access to text in voice.
/// Restricted to <see cref="IsStaffOnly"/>.
/// </summary>
TextInVoiceEnabled,
/// <summary>
/// Guild can set an animated banner.
/// Needs Premium Tier 3 (<see cref="PremiumTier.TierThree"/>).
/// </summary>
CanSetAnimatedBanner,
/// <summary>
/// Guild can set an animated banner.
/// Needs Premium Tier 3 (<see cref="PremiumTier.TierThree"/>).
/// </summary>
[DiscordDeprecated("Feature was removed"), Obsolete]
CanSetChannelBanner,
/// <summary>
/// Allows members to customize their avatar, banner and bio for that server.
/// </summary>
HasMemberProfiles,
/// <summary>
/// Guild is restricted to users with the <see cref="UserFlags.Staff"/> badge.
/// </summary>
IsStaffOnly,
/// <summary>
/// Guild can use and setup the experimental auto moderation feature.
/// </summary>
CanSetupAutoModeration,
/// <summary>
/// Guild has access to home.
/// </summary>
GuildHomeTest,
/// <summary>
/// Guild has disabled invites.
/// </summary>
InvitesDisabled,
/// <summary>
/// Currently unknown.
/// </summary>
ActivitiesAlpha,
/// <summary>
/// Currently unknown.
/// </summary>
ActivitiesEmployee,
/// <summary>
/// Currently unknown.
/// </summary>
ActivitiesInternalDev,
/// <summary>
/// Currently unknown.
/// </summary>
AutomodTriggerKeywordFilter,
/// <summary>
/// Currently unknown.
/// </summary>
AutomodTriggerMlSpamFilter,
/// <summary>
/// Currently unknown.
/// </summary>
AutomodTriggerSpamLinkFilterGuild,
/// <summary>
/// Currently unknown.
/// </summary>
AutomodDefaultList,
/// <summary>
/// Currently unknown.
/// </summary>
Bfg,
/// <summary>
/// Currently unknown.
/// </summary>
BoostingTiersExperimentMediumGuild,
/// <summary>
/// Currently unknown.
/// </summary>
BoostingTiersExperimentSmallGuild,
/// <summary>
/// Guild has early access features for bot and library developers.
/// </summary>
BotDeveloperEarlyAccess,
/// <summary>
/// Currently unknown.
/// </summary>
CreatorMonetizable,
/// <summary>
/// Currently unknown.
/// </summary>
CreatorMonetizableDisabled,
/// <summary>
/// Currently unknown.
/// </summary>
CreatorMonetizableProvisional,
/// <summary>
/// Currently unknown.
/// </summary>
CreatorMonetizableWhiteGlove,
/// <summary>
/// Currently unknown.
/// </summary>
CreatorMonetizationApplicationAllowlist,
/// <summary>
/// Guild is set as a support server for an app in App Directory.
/// </summary>
DeveloperSupportServer,
/// <summary>
/// Guild was previously in the 2021-11_activities_baseline_engagement_bundle experiment.
/// </summary>
ExposedToActivitiesWtpExperiment,
/// <summary>
/// Guild had early access to the user timeouts.
/// </summary>
GuildCommunicationDisabledGuilds,
/// <summary>
/// Currently unknown.
/// </summary>
DisableGuildCommunication,
/// <summary>
/// Guild has access to the Home feature.
/// </summary>
GuildHomeOverride,
/// <summary>
/// Guild had early access to the Automod Default List.
/// </summary>
GuildAutomodDefaultList,
/// <summary>
/// Guild had early access to approving membership manually.
/// </summary>
GuildMemberVerificationExperiment,
/// <summary>
/// Guilds was previously in the 2022-05_mobile_web_role_subscription_purchase_page experiment.
/// </summary>
GuildRoleSubscriptionPurchaseFeedbackLoop,
/// <summary>
/// Guild was previously in the 2022-01_guild_role_subscription_trials experiment.
/// </summary>
GuildRoleSubscriptionTrials,
/// <summary>
/// Guild previously had access to voice channel activities and can bypass the boost level requirement.
/// </summary>
HadEarlyActivitiesAccess,
/// <summary>
/// Allows the guild to have 1,000+ active threads.
/// </summary>
IncreasedThreadLimit,
/// <summary>
/// Guild was previously in the 2022-05_mobile_web_role_subscription_purchase_page experiment.
/// </summary>
MobileWebRoleSubscriptionPurchasePage,
/// <summary>
/// Shards connections to the guild to different nodes that relay information between each other.
/// </summary>
RelayEnabled,
/// <summary>
/// Currently unknown.
/// </summary>
RestrictSpamRiskGuilds,
/// <summary>
/// Allows guild's members to purchase role subscriptions.
/// </summary>
RoleSubscriptionsAvailableForPurchase,
/// <summary>
/// Used by bot developers to test their bots with threads in guilds with 5 or less members and a bot.
/// </summary>
ThreadsEnabledTesting,
/// <summary>
/// Guild had early access to the voice channel effects.
/// </summary>
VoiceChannelEffects,
/// <summary>
/// Guild had early access to the soundboard feature.
/// </summary>
Soundboard,
/// <summary>
/// Ability to create and use store channels.
/// </summary>
[DiscordDeprecated("This feature is depcreated"), Obsolete]
Commerce,
/// <summary>
/// Currently unknown.
/// </summary>
[DiscordDeprecated("This feature is depcreated"), Obsolete]
ExposedToBoostingTiersExperiment,
/// <summary>
/// Deprecated in favor of Community.
/// </summary>
[DiscordDeprecated("This feature is depcreated"), Obsolete]
PublicDisabled,
/// <summary>
/// Deprecated in favor of Community.
/// </summary>
[DiscordDeprecated("This feature is depcreated"), Obsolete]
Public,
/// <summary>
/// The guild can use the seven-day archive time for threads.
/// </summary>
[DiscordDeprecated("This feature is depcreated"), Obsolete]
SevenDayThreadArchive,
/// <summary>
/// The guild can use the three-day archive time for threads.
/// </summary>
[DiscordDeprecated("This feature is depcreated"), Obsolete]
ThreeDayThreadArchive,
/// <summary>
/// Previously used to control which servers were displayed under the "Featured" category in Discovery.
/// </summary>
[DiscordDeprecated("This feature is depcreated"), Obsolete]
Featurable,
/// <summary>
/// Shards connections to the guild to different nodes that relay information between each other.
/// </summary>
[DiscordDeprecated("This feature is depcreated"), Obsolete]
ForceRelay,
/// <summary>
/// Currently unknown.
/// </summary>
[DiscordDeprecated("This feature is depcreated"), Obsolete]
Lurkable,
/// <summary>
/// Created for the Fortnite server blackout event on Oct 13, 2019, when viewing the member list it would show "There's nothing to see here.".
/// </summary>
[DiscordDeprecated("This feature is depcreated"), Obsolete]
MemberListDisabled,
[DiscordInExperiment]
CanUseCreatorStorePage,
[DiscordInExperiment]
CanUseBurstReactions,
[DiscordInExperiment]
HadGuildOnBoardingEverEnabled,
[DiscordInExperiment]
ChannelHighlightsDisabled,
[DiscordInExperiment]
ChannelHighlights,
[DiscordInExperiment]
CreatorMonetizableRestricted,
[DiscordUnreleased]
VoiceInThreadsEnabled,
[DiscordInExperiment("Feature related to automod.")]
RaidAlertsEnabled,
[DiscordInExperiment("Was recently added to determine whether guilds uses the newer permission system for application commands.")]
UsesApplicationCommandsPermissionsV2,
}
diff --git a/DisCatSharp/Entities/Guild/DiscordGuild.cs b/DisCatSharp/Entities/Guild/DiscordGuild.cs
index 617c82f8c..3b84b4ab7 100644
--- a/DisCatSharp/Entities/Guild/DiscordGuild.cs
+++ b/DisCatSharp/Entities/Guild/DiscordGuild.cs
@@ -1,2196 +1,2196 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using DisCatSharp.Enums;
using DisCatSharp.Exceptions;
using DisCatSharp.Net;
using DisCatSharp.Net.Abstractions;
using DisCatSharp.Net.Models;
using DisCatSharp.Net.Serialization;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a Discord guild.
/// </summary>
public partial class DiscordGuild : SnowflakeObject, IEquatable<DiscordGuild>
{
/// <summary>
/// Gets the guild's name.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; internal set; }
/// <summary>
/// Gets the guild icon's hash.
/// </summary>
[JsonProperty("icon", NullValueHandling = NullValueHandling.Ignore)]
public string IconHash { get; internal set; }
/// <summary>
/// Gets the guild icon's url.
/// </summary>
[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;
/// <summary>
/// Gets the guild splash's hash.
/// </summary>
[JsonProperty("splash", NullValueHandling = NullValueHandling.Ignore)]
public string SplashHash { get; internal set; }
/// <summary>
/// Gets the guild splash's url.
/// </summary>
[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;
/// <summary>
/// Gets the guild discovery splash's hash.
/// </summary>
[JsonProperty("discovery_splash", NullValueHandling = NullValueHandling.Ignore)]
public string DiscoverySplashHash { get; internal set; }
/// <summary>
/// Gets the guild discovery splash's url.
/// </summary>
[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;
/// <summary>
/// Gets the preferred locale of this guild.
/// <para>This is used for server discovery, interactions and notices from Discord. Defaults to en-US.</para>
/// </summary>
[JsonProperty("preferred_locale", NullValueHandling = NullValueHandling.Ignore)]
public string PreferredLocale { get; internal set; }
/// <summary>
/// Gets the ID of the guild's owner.
/// </summary>
[JsonProperty("owner_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong OwnerId { get; internal set; }
/// <summary>
/// Gets the guild's owner.
/// </summary>
[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();
/// <summary>
/// Gets permissions for the user in the guild (does not include channel overrides)
/// </summary>
[JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)]
public Permissions? Permissions { get; set; }
/// <summary>
/// Gets the guild's voice region ID.
/// </summary>
[JsonProperty("region", NullValueHandling = NullValueHandling.Ignore)]
internal string VoiceRegionId { get; set; }
/// <summary>
/// Gets the guild's voice region.
/// </summary>
[JsonIgnore]
public DiscordVoiceRegion VoiceRegion
=> this.Discord.VoiceRegions[this.VoiceRegionId];
/// <summary>
/// Gets the guild's AFK voice channel ID.
/// </summary>
[JsonProperty("afk_channel_id", NullValueHandling = NullValueHandling.Ignore)]
internal ulong AfkChannelId { get; set; }
/// <summary>
/// Gets the guild's AFK voice channel.
/// </summary>
[JsonIgnore]
public DiscordChannel AfkChannel
=> this.GetChannel(this.AfkChannelId);
/// <summary>
/// List of <see cref="DisCatSharp.Entities.DiscordApplicationCommand"/>.
/// Null if DisCatSharp.ApplicationCommands is not used or no guild commands are registered.
/// </summary>
[JsonIgnore]
public ReadOnlyCollection<DiscordApplicationCommand> RegisteredApplicationCommands
=> new(this.InternalRegisteredApplicationCommands);
[JsonIgnore]
internal List<DiscordApplicationCommand> InternalRegisteredApplicationCommands { get; set; } = new();
/// <summary>
/// Gets the guild's AFK timeout.
/// </summary>
[JsonProperty("afk_timeout", NullValueHandling = NullValueHandling.Ignore)]
public int AfkTimeout { get; internal set; }
/// <summary>
/// Gets the guild's verification level.
/// </summary>
[JsonProperty("verification_level", NullValueHandling = NullValueHandling.Ignore)]
public VerificationLevel VerificationLevel { get; internal set; }
/// <summary>
/// Gets the guild's default notification settings.
/// </summary>
[JsonProperty("default_message_notifications", NullValueHandling = NullValueHandling.Ignore)]
public DefaultMessageNotifications DefaultMessageNotifications { get; internal set; }
/// <summary>
/// Gets the guild's explicit content filter settings.
/// </summary>
[JsonProperty("explicit_content_filter")]
public ExplicitContentFilter ExplicitContentFilter { get; internal set; }
/// <summary>
/// Gets the guild's nsfw level.
/// </summary>
[JsonProperty("nsfw_level")]
public NsfwLevel NsfwLevel { get; internal set; }
/// <summary>
/// Gets the system channel id.
/// </summary>
[JsonProperty("system_channel_id", NullValueHandling = NullValueHandling.Include)]
internal ulong? SystemChannelId { get; set; }
/// <summary>
/// Gets the channel where system messages (such as boost and welcome messages) are sent.
/// </summary>
[JsonIgnore]
public DiscordChannel SystemChannel => this.SystemChannelId.HasValue
? this.GetChannel(this.SystemChannelId.Value)
: null;
/// <summary>
/// Gets the settings for this guild's system channel.
/// </summary>
[JsonProperty("system_channel_flags")]
public SystemChannelFlags SystemChannelFlags { get; internal set; }
/// <summary>
/// Gets whether this guild's widget is enabled.
/// </summary>
[JsonProperty("widget_enabled", NullValueHandling = NullValueHandling.Ignore)]
public bool? WidgetEnabled { get; internal set; }
/// <summary>
/// Gets the widget channel id.
/// </summary>
[JsonProperty("widget_channel_id", NullValueHandling = NullValueHandling.Ignore)]
internal ulong? WidgetChannelId { get; set; }
/// <summary>
/// Gets the safety alerts channel id.
/// </summary>
[JsonProperty("safety_alerts_channel_id", NullValueHandling = NullValueHandling.Ignore)]
internal ulong? SafetyAlertsChannelId { get; set; }
/// <summary>
/// Gets the widget channel for this guild.
/// </summary>
[JsonIgnore]
public DiscordChannel WidgetChannel => this.WidgetChannelId.HasValue
? this.GetChannel(this.WidgetChannelId.Value)
: null;
/// <summary>
/// Gets the rules channel id.
/// </summary>
[JsonProperty("rules_channel_id")]
internal ulong? RulesChannelId { get; set; }
/// <summary>
/// Gets the rules channel for this guild.
/// <para>This is only available if the guild is considered "discoverable".</para>
/// </summary>
[JsonIgnore]
public DiscordChannel RulesChannel => this.RulesChannelId.HasValue
? this.GetChannel(this.RulesChannelId.Value)
: null;
/// <summary>
/// Gets the public updates channel id.
/// </summary>
[JsonProperty("public_updates_channel_id")]
internal ulong? PublicUpdatesChannelId { get; set; }
/// <summary>
/// Gets the public updates channel (where admins and moderators receive messages from Discord) for this guild.
/// <para>This is only available if the guild is considered "discoverable".</para>
/// </summary>
[JsonIgnore]
public DiscordChannel PublicUpdatesChannel => this.PublicUpdatesChannelId.HasValue
? this.GetChannel(this.PublicUpdatesChannelId.Value)
: null;
/// <summary>
/// Gets the application id of this guild if it is bot created.
/// </summary>
[JsonProperty("application_id")]
public ulong? ApplicationId { get; internal set; }
/// <summary>
/// Gets a collection of this guild's roles.
/// </summary>
[JsonIgnore]
public IReadOnlyDictionary<ulong, DiscordRole> Roles => new ReadOnlyConcurrentDictionary<ulong, DiscordRole>(this.RolesInternal);
[JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary<ulong, DiscordRole> RolesInternal;
/// <summary>
/// Gets a collection of this guild's stickers.
/// </summary>
[JsonIgnore]
public IReadOnlyDictionary<ulong, DiscordSticker> Stickers => new ReadOnlyConcurrentDictionary<ulong, DiscordSticker>(this.StickersInternal);
[JsonProperty("stickers", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary<ulong, DiscordSticker> StickersInternal;
/// <summary>
/// Gets a collection of this guild's emojis.
/// </summary>
[JsonIgnore]
public IReadOnlyDictionary<ulong, DiscordEmoji> Emojis => new ReadOnlyConcurrentDictionary<ulong, DiscordEmoji>(this.EmojisInternal);
[JsonProperty("emojis", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary<ulong, DiscordEmoji> EmojisInternal;
/// <summary>
/// Gets a collection of this guild's features.
/// </summary>
[JsonProperty("features", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<string> RawFeatures { get; internal set; }
/// <summary>
/// Gets the guild's features.
/// </summary>
[JsonIgnore]
public GuildFeatures Features => new(this);
/// <summary>
/// Gets the required multi-factor authentication level for this guild.
/// </summary>
[JsonProperty("mfa_level", NullValueHandling = NullValueHandling.Ignore)]
public MfaLevel MfaLevel { get; internal set; }
/// <summary>
/// Gets this guild's join date.
/// </summary>
[JsonProperty("joined_at", NullValueHandling = NullValueHandling.Ignore)]
public DateTimeOffset JoinedAt { get; internal set; }
/// <summary>
/// Gets whether this guild is considered to be a large guild.
/// </summary>
[JsonProperty("large", NullValueHandling = NullValueHandling.Ignore)]
public bool IsLarge { get; internal set; }
/// <summary>
/// Gets whether this guild is unavailable.
/// </summary>
[JsonProperty("unavailable", NullValueHandling = NullValueHandling.Ignore)]
public bool IsUnavailable { get; internal set; }
/// <summary>
/// Gets the total number of members in this guild.
/// </summary>
[JsonProperty("member_count", NullValueHandling = NullValueHandling.Ignore)]
public int MemberCount { get; internal set; }
/// <summary>
/// Gets the maximum amount of members allowed for this guild.
/// </summary>
[JsonProperty("max_members")]
public int? MaxMembers { get; internal set; }
/// <summary>
/// Gets the maximum amount of presences allowed for this guild.
/// </summary>
[JsonProperty("max_presences")]
public int? MaxPresences { get; internal set; }
/// <summary>
/// Gets the approximate number of members in this guild, when using <see cref="DiscordClient.GetGuildAsync(ulong, bool?, bool)"/> and having withCounts set to true.
/// </summary>
[JsonProperty("approximate_member_count", NullValueHandling = NullValueHandling.Ignore)]
public int? ApproximateMemberCount { get; internal set; }
/// <summary>
/// Gets the approximate number of presences in this guild, when using <see cref="DiscordClient.GetGuildAsync(ulong, bool?, bool)"/> and having withCounts set to true.
/// </summary>
[JsonProperty("approximate_presence_count", NullValueHandling = NullValueHandling.Ignore)]
public int? ApproximatePresenceCount { get; internal set; }
/// <summary>
/// Gets the maximum amount of users allowed per video channel.
/// </summary>
[JsonProperty("max_video_channel_users", NullValueHandling = NullValueHandling.Ignore)]
public int? MaxVideoChannelUsers { get; internal set; }
/// <summary>
/// Gets the maximum amount of users allowed per video stage channel.
/// </summary>
[JsonProperty("max_stage_video_channel_users", NullValueHandling = NullValueHandling.Ignore)]
public int? MaxStageVideoChannelUsers { get; internal set; }
/// <summary>
/// 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.
/// </summary>
[JsonIgnore]
public IReadOnlyDictionary<ulong, DiscordVoiceState> VoiceStates => new ReadOnlyConcurrentDictionary<ulong, DiscordVoiceState>(this.VoiceStatesInternal);
[JsonProperty("voice_states", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary<ulong, DiscordVoiceState> VoiceStatesInternal;
/// <summary>
/// Gets a dictionary of all the members that belong to this guild. The dictionary's key is the member ID.
/// </summary>
[JsonIgnore]
public IReadOnlyDictionary<ulong, DiscordMember> Members => new ReadOnlyConcurrentDictionary<ulong, DiscordMember>(this.MembersInternal);
[JsonProperty("members", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary<ulong, DiscordMember> MembersInternal;
/// <summary>
/// Gets a dictionary of all the channels associated with this guild. The dictionary's key is the channel ID.
/// </summary>
[JsonIgnore]
public IReadOnlyDictionary<ulong, DiscordChannel> Channels => new ReadOnlyConcurrentDictionary<ulong, DiscordChannel>(this.ChannelsInternal);
[JsonProperty("channels", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary<ulong, DiscordChannel> ChannelsInternal;
internal ConcurrentDictionary<string, DiscordInvite> Invites;
/// <summary>
/// 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.
/// </summary>
[JsonIgnore]
public IReadOnlyDictionary<ulong, DiscordThreadChannel> Threads { get; internal set; }
[JsonProperty("threads", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary<ulong, DiscordThreadChannel> ThreadsInternal = new();
/// <summary>
/// Gets a dictionary of all active stage instances. The dictionary's key is the stage ID.
/// </summary>
[JsonIgnore]
public IReadOnlyDictionary<ulong, DiscordStageInstance> StageInstances { get; internal set; }
[JsonProperty("stage_instances", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary<ulong, DiscordStageInstance> StageInstancesInternal = new();
/// <summary>
/// Gets a dictionary of all scheduled events.
/// </summary>
[JsonIgnore]
public IReadOnlyDictionary<ulong, DiscordScheduledEvent> ScheduledEvents { get; internal set; }
[JsonProperty("guild_scheduled_events", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary<ulong, DiscordScheduledEvent> ScheduledEventsInternal = new();
/// <summary>
/// Gets the guild member for current user.
/// </summary>
[JsonIgnore]
public DiscordMember CurrentMember
=> this._currentMemberLazy.Value;
[JsonIgnore]
private readonly Lazy<DiscordMember> _currentMemberLazy;
/// <summary>
/// Gets the @everyone role for this guild.
/// </summary>
[JsonIgnore]
public DiscordRole EveryoneRole
=> this.GetRole(this.Id);
[JsonIgnore]
internal bool IsOwnerInternal;
/// <summary>
/// Gets whether the current user is the guild's owner.
/// </summary>
[JsonProperty("owner", NullValueHandling = NullValueHandling.Ignore)]
public bool IsOwner
{
get => this.IsOwnerInternal || this.OwnerId == this.Discord.CurrentUser.Id;
internal set => this.IsOwnerInternal = value;
}
/// <summary>
/// Gets the vanity URL code for this guild, when applicable.
/// </summary>
[JsonProperty("vanity_url_code")]
public string VanityUrlCode { get; internal set; }
/// <summary>
/// Gets the guild description, when applicable.
/// </summary>
[JsonProperty("description")]
public string Description { get; internal set; }
/// <summary>
/// Gets this guild's banner hash, when applicable.
/// </summary>
[JsonProperty("banner")]
public string BannerHash { get; internal set; }
/// <summary>
/// Gets this guild's banner in url form.
/// </summary>
[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;
/// <summary>
/// Whether this guild has the community feature enabled.
/// </summary>
[JsonIgnore]
public bool IsCommunity => this.Features.HasFeature(GuildFeaturesEnum.HasCommunityEnabled);
/// <summary>
/// Whether this guild has enabled the welcome screen.
/// </summary>
[JsonIgnore]
public bool HasWelcomeScreen => this.Features.HasFeature(GuildFeaturesEnum.HasWelcomeScreenEnabled);
/// <summary>
/// Whether this guild has enabled membership screening.
/// </summary>
[JsonIgnore]
public bool HasMemberVerificationGate => this.Features.HasFeature(GuildFeaturesEnum.HasMembershipScreeningEnabled);
/// <summary>
/// Gets this guild's premium tier (Nitro boosting).
/// </summary>
[JsonProperty("premium_tier")]
public PremiumTier PremiumTier { get; internal set; }
/// <summary>
/// Gets the amount of members that boosted this guild.
/// </summary>
[JsonProperty("premium_subscription_count", NullValueHandling = NullValueHandling.Ignore)]
public int? PremiumSubscriptionCount { get; internal set; }
/// <summary>
/// Whether the premium progress bar is enabled.
/// </summary>
[JsonProperty("premium_progress_bar_enabled", NullValueHandling = NullValueHandling.Ignore)]
public bool PremiumProgressBarEnabled { get; internal set; }
/// <summary>
/// Gets whether this guild is designated as NSFW.
/// </summary>
[JsonProperty("nsfw", NullValueHandling = NullValueHandling.Ignore)]
public bool IsNsfw { get; internal set; }
/// <summary>
/// Gets this guild's hub type, if applicable.
/// </summary>
[JsonProperty("hub_type", NullValueHandling = NullValueHandling.Ignore)]
public HubType HubType { get; internal set; }
/// <summary>
/// Gets a dictionary of all by position ordered channels associated with this guild. The dictionary's key is the channel ID.
/// </summary>
[JsonIgnore]
public IReadOnlyDictionary<ulong, DiscordChannel> OrderedChannels => new ReadOnlyDictionary<ulong, DiscordChannel>(this.InternalSortChannels());
/// <summary>
/// Sorts the channels.
/// </summary>
private Dictionary<ulong, DiscordChannel> InternalSortChannels()
{
Dictionary<ulong, DiscordChannel> 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;
}
/// <summary>
/// Gets an ordered <see cref="DiscordChannel"/> list out of the channel cache.
/// Returns a Dictionary where the key is an ulong and can be mapped to <see cref="ChannelType.Category"/> <see cref="DiscordChannel"/>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 <see cref="DiscordChannel"/>.
/// </summary>
/// <returns>A ordered list of categories with its channels</returns>
public Dictionary<ulong, List<DiscordChannel>> GetOrderedChannels()
{
IReadOnlyList<DiscordChannel> rawChannels = this.ChannelsInternal.Values.ToList();
Dictionary<ulong, List<DiscordChannel>> orderedChannels = new()
{
{ 0, new List<DiscordChannel>() }
};
foreach (var channel in rawChannels.Where(c => c.Type == ChannelType.Category).OrderBy(c => c.Position))
{
orderedChannels.Add(channel.Id, new List<DiscordChannel>());
}
foreach (var channel in rawChannels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Text || c.Type == ChannelType.News || c.Type == ChannelType.Forum)).OrderBy(c => c.Position))
{
orderedChannels[channel.ParentId.Value].Add(channel);
}
foreach (var channel in rawChannels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position))
{
orderedChannels[channel.ParentId.Value].Add(channel);
}
foreach (var channel in rawChannels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Text || c.Type == ChannelType.News || c.Type == ChannelType.Forum)).OrderBy(c => c.Position))
{
orderedChannels[0].Add(channel);
}
foreach (var channel in rawChannels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position))
{
orderedChannels[0].Add(channel);
}
return orderedChannels;
}
/// <summary>
/// Gets an ordered <see cref="DiscordChannel"/> list.
/// Returns a Dictionary where the key is an ulong and can be mapped to <see cref="ChannelType.Category"/> <see cref="DiscordChannel"/>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 <see cref="DiscordChannel"/>.
/// </summary>
/// <returns>A ordered list of categories with its channels</returns>
public async Task<Dictionary<ulong, List<DiscordChannel>>> GetOrderedChannelsAsync()
{
var rawChannels = await this.Discord.ApiClient.GetGuildChannelsAsync(this.Id);
Dictionary<ulong, List<DiscordChannel>> orderedChannels = new()
{
{ 0, new List<DiscordChannel>() }
};
foreach (var channel in rawChannels.Where(c => c.Type == ChannelType.Category).OrderBy(c => c.Position))
{
orderedChannels.Add(channel.Id, new List<DiscordChannel>());
}
foreach (var channel in rawChannels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Text || c.Type == ChannelType.News || c.Type == ChannelType.Forum)).OrderBy(c => c.Position))
{
orderedChannels[channel.ParentId.Value].Add(channel);
}
foreach (var channel in rawChannels.Where(c => c.ParentId.HasValue && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position))
{
orderedChannels[channel.ParentId.Value].Add(channel);
}
foreach (var channel in rawChannels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Text || c.Type == ChannelType.News || c.Type == ChannelType.Forum)).OrderBy(c => c.Position))
{
orderedChannels[0].Add(channel);
}
foreach (var channel in rawChannels.Where(c => !c.ParentId.HasValue && c.Type != ChannelType.Category && (c.Type == ChannelType.Voice || c.Type == ChannelType.Stage)).OrderBy(c => c.Position))
{
orderedChannels[0].Add(channel);
}
return orderedChannels;
}
/// <summary>
/// Whether it is synced.
/// </summary>
[JsonIgnore]
internal bool IsSynced { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordGuild"/> class.
/// </summary>
internal DiscordGuild()
{
this._currentMemberLazy = new Lazy<DiscordMember>(() => this.MembersInternal != null && this.MembersInternal.TryGetValue(this.Discord.CurrentUser.Id, out var member) ? member : null);
this.Invites = new ConcurrentDictionary<string, DiscordInvite>();
this.Threads = new ReadOnlyConcurrentDictionary<ulong, DiscordThreadChannel>(this.ThreadsInternal);
this.StageInstances = new ReadOnlyConcurrentDictionary<ulong, DiscordStageInstance>(this.StageInstancesInternal);
this.ScheduledEvents = new ReadOnlyConcurrentDictionary<ulong, DiscordScheduledEvent>(this.ScheduledEventsInternal);
}
#region Guild Methods
/// <summary>
/// Searches the current guild for members who's display name start with the specified name.
/// </summary>
/// <param name="name">The name to search for.</param>
/// <param name="limit">The maximum amount of members to return. Max 1000. Defaults to 1.</param>
/// <returns>The members found, if any.</returns>
public Task<IReadOnlyList<DiscordMember>> SearchMembersAsync(string name, int? limit = 1)
=> this.Discord.ApiClient.SearchMembersAsync(this.Id, name, limit);
/// <summary>
/// Adds a new member to this guild
/// </summary>
/// <param name="user">User to add</param>
/// <param name="accessToken">User's access token (OAuth2)</param>
/// <param name="nickname">new nickname</param>
/// <param name="roles">new roles</param>
/// <param name="muted">whether this user has to be muted</param>
/// <param name="deaf">whether this user has to be deafened</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.CreateInstantInvite" /> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the <paramref name="user"/> or <paramref name="accessToken"/> is not found.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task AddMemberAsync(DiscordUser user, string accessToken, string nickname = null, IEnumerable<DiscordRole> roles = null,
bool muted = false, bool deaf = false)
=> this.Discord.ApiClient.AddGuildMemberAsync(this.Id, user.Id, accessToken, nickname, roles, muted, deaf);
/// <summary>
/// Deletes this guild. Requires the caller to be the owner of the guild.
/// </summary>
/// <exception cref="UnauthorizedException">Thrown when the client is not the owner of the guild.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task DeleteAsync()
=> this.Discord.ApiClient.DeleteGuildAsync(this.Id);
/// <summary>
/// Enables the mfa requirement for this guild.
/// </summary>
/// <param name="reason">The audit log reason.</param>
/// <exception cref="UnauthorizedException">Thrown when the current user is not the guilds owner.</exception>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task EnableMfaAsync(string reason = null)
=> this.IsOwner ? this.Discord.ApiClient.EnableGuildMfaAsync(this.Id, reason) : throw new Exception("The current user does not own the guild.");
/// <summary>
/// Disables the mfa requirement for this guild.
/// </summary>
/// <param name="reason">The audit log reason.</param>
/// <exception cref="UnauthorizedException">Thrown when the current user is not the guilds owner.</exception>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task DisableMfaAsync(string reason = null)
=> this.IsOwner ? this.Discord.ApiClient.DisableGuildMfaAsync(this.Id, reason) : throw new Exception("The current user does not own the guild.");
/// <summary>
/// Modifies this guild.
/// </summary>
/// <param name="action">Action to perform on this guild.</param>
/// <returns>The modified guild object.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageGuild"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordGuild> ModifyAsync(Action<GuildEditModel> action)
{
var mdl = new GuildEditModel();
action(mdl);
var afkChannelId = mdl.PublicUpdatesChannel
.MapOrNull<ulong?>(c => c.Type != ChannelType.Voice
? throw new ArgumentException("AFK channel needs to be a text channel.")
: c.Id);
static Optional<ulong?> ChannelToId(Optional<DiscordChannel> ch, string name)
=> ch.MapOrNull<ulong?>(c => c.Type != ChannelType.Text && c.Type != ChannelType.News
? throw new ArgumentException($"{name} channel needs to be a text channel.")
: c.Id);
var rulesChannelId = ChannelToId(mdl.RulesChannel, "Rules");
var publicUpdatesChannelId = ChannelToId(mdl.PublicUpdatesChannel, "Public updates");
var systemChannelId = ChannelToId(mdl.SystemChannel, "System");
var iconb64 = ImageTool.Base64FromStream(mdl.Icon);
var splashb64 = ImageTool.Base64FromStream(mdl.Splash);
var bannerb64 = ImageTool.Base64FromStream(mdl.Banner);
var discoverySplash64 = ImageTool.Base64FromStream(mdl.DiscoverySplash);
return await this.Discord.ApiClient.ModifyGuildAsync(this.Id, mdl.Name,
mdl.VerificationLevel, mdl.DefaultMessageNotifications, mdl.MfaLevel, mdl.ExplicitContentFilter,
afkChannelId, mdl.AfkTimeout, iconb64, mdl.Owner.Map(e => e.Id), splashb64,
systemChannelId, mdl.SystemChannelFlags, publicUpdatesChannelId, rulesChannelId,
mdl.Description, bannerb64, discoverySplash64, mdl.PreferredLocale, mdl.PremiumProgressBarEnabled, mdl.AuditLogReason).ConfigureAwait(false);
}
/// <summary>
/// Modifies the community settings async.
/// This sets <see cref="VerificationLevel.High"/> if not highest and <see cref="ExplicitContentFilter.AllMembers"/>.
/// </summary>
/// <param name="enabled">If true, enables <see cref="GuildFeaturesEnum.HasCommunityEnabled"/>.</param>
/// <param name="rulesChannel">The rules channel.</param>
/// <param name="publicUpdatesChannel">The public updates channel.</param>
/// <param name="preferredLocale">The preferred locale. Defaults to en-US.</param>
/// <param name="description">The description.</param>
/// <param name="defaultMessageNotifications">The default message notifications. Defaults to <see cref="DefaultMessageNotifications.MentionsOnly"/></param>
/// <param name="reason">The audit log reason.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.Administrator"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordGuild> ModifyCommunitySettingsAsync(bool enabled, DiscordChannel rulesChannel, DiscordChannel publicUpdatesChannel, string preferredLocale = "en-US", string description = null, DefaultMessageNotifications defaultMessageNotifications = DefaultMessageNotifications.MentionsOnly, string reason = null)
{
var verificationLevel = this.VerificationLevel;
if (this.VerificationLevel != VerificationLevel.Highest)
{
verificationLevel = VerificationLevel.High;
}
var explicitContentFilter = ExplicitContentFilter.AllMembers;
static Optional<ulong?> ChannelToId(DiscordChannel ch, string name)
=> ch == null ? null :
ch.Type != ChannelType.Text && ch.Type != ChannelType.News
? throw new ArgumentException($"{name} channel needs to be a text channel.")
: ch.Id;
var rulesChannelId = ChannelToId(rulesChannel, "Rules");
var publicUpdatesChannelId = ChannelToId(publicUpdatesChannel, "Public updates");
List<string> features = new();
var rfeatures = this.RawFeatures.ToList();
if (!this.RawFeatures.Contains("COMMUNITY") && enabled)
{
rfeatures.Add("COMMUNITY");
}
else if (this.RawFeatures.Contains("COMMUNITY") && !enabled)
{
rfeatures.Remove("COMMUNITY");
}
features = rfeatures;
return await this.Discord.ApiClient.ModifyGuildCommunitySettingsAsync(this.Id, features, rulesChannelId, publicUpdatesChannelId, preferredLocale, description, defaultMessageNotifications, explicitContentFilter, verificationLevel, reason).ConfigureAwait(false);
}
/// <summary>
/// Modifies the safety alerts settings async.
/// </summary>
/// <param name="enabled">If true, enables <see cref="GuildFeaturesEnum.HasCommunityEnabled"/>.</param>
/// <param name="safetyAlertsChannel">The safety alerts channel.</param>
/// <param name="reason">The audit log reason.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageGuild"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordGuild> ModifySafetyAlertsSettingsAsync(bool enabled, DiscordChannel safetyAlertsChannel, string reason = null)
{
static Optional<ulong?> ChannelToId(DiscordChannel ch, string name)
=> ch == null ? null :
ch.Type != ChannelType.Text && ch.Type != ChannelType.News
? throw new ArgumentException($"{name} channel needs to be a text channel.")
: ch.Id;
var safetyAlertsChannelId = ChannelToId(safetyAlertsChannel, "Safety Alerts");
List<string> features = new();
var rfeatures = this.RawFeatures.ToList();
if (!this.RawFeatures.Contains("RAID_ALERTS_ENABLED") && enabled)
{
rfeatures.Add("RAID_ALERTS_ENABLED");
}
else if (this.RawFeatures.Contains("RAID_ALERTS_ENABLED") && !enabled)
{
rfeatures.Remove("RAID_ALERTS_ENABLED");
}
features = rfeatures;
return await this.Discord.ApiClient.ModifyGuildSafetyAlertsSettingsAsync(this.Id, features, safetyAlertsChannelId, reason).ConfigureAwait(false);
}
/// <summary>
/// Enables invites for the guild.
/// </summary>
/// <param name="reason">The audit log reason.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageGuild"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordGuild> EnableInvitesAsync(string reason = null)
{
List<string> features = new();
var rfeatures = this.RawFeatures.ToList();
if (this.Features.HasFeature(GuildFeaturesEnum.InvitesDisabled))
rfeatures.Remove("INVITES_DISABLED");
features = rfeatures;
return await this.Discord.ApiClient.ModifyGuildFeaturesAsync(this.Id, features, reason);
}
/// <summary>
/// Disables invites for the guild.
/// </summary>
/// <param name="reason"></param>
/// <returns></returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageGuild"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordGuild> DisableInvitesAsync(string reason = null)
{
List<string> features = new();
var rfeatures = this.RawFeatures.ToList();
if (!this.Features.HasFeature(GuildFeaturesEnum.InvitesDisabled))
rfeatures.Add("INVITES_DISABLED");
features = rfeatures;
return await this.Discord.ApiClient.ModifyGuildFeaturesAsync(this.Id, features, reason);
}
/// <summary>
/// Timeout a specified member in this guild.
/// </summary>
/// <param name="memberId">Member to timeout.</param>
/// <param name="until">The datetime offset to time out the user. Up to 28 days.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ModerateMembers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
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);
/// <summary>
/// Timeout a specified member in this guild.
/// </summary>
/// <param name="memberId">Member to timeout.</param>
/// <param name="until">The timespan to time out the user. Up to 28 days.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ModerateMembers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task TimeoutAsync(ulong memberId, TimeSpan until, string reason = null)
=> this.TimeoutAsync(memberId, DateTimeOffset.UtcNow + until, reason);
/// <summary>
/// Timeout a specified member in this guild.
/// </summary>
/// <param name="memberId">Member to timeout.</param>
/// <param name="until">The datetime to time out the user. Up to 28 days.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ModerateMembers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task TimeoutAsync(ulong memberId, DateTime until, string reason = null)
=> this.TimeoutAsync(memberId, until.ToUniversalTime() - DateTime.UtcNow, reason);
/// <summary>
/// Removes the timeout from a specified member in this guild.
/// </summary>
/// <param name="memberId">Member to remove the timeout from.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ModerateMembers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task RemoveTimeoutAsync(ulong memberId, string reason = null)
=> this.Discord.ApiClient.ModifyTimeoutAsync(this.Id, memberId, null, reason);
/// <summary>
/// Bans a specified member from this guild.
/// </summary>
/// <param name="member">Member to ban.</param>
/// <param name="deleteMessageDays">How many days to remove messages from.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.BanMembers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task BanMemberAsync(DiscordMember member, int deleteMessageDays = 0, string reason = null)
=> this.Discord.ApiClient.CreateGuildBanAsync(this.Id, member.Id, deleteMessageDays, reason);
/// <summary>
/// Bans a specified user by ID. This doesn't require the user to be in this guild.
/// </summary>
/// <param name="userId">ID of the user to ban.</param>
/// <param name="deleteMessageDays">How many days to remove messages from.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.BanMembers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task BanMemberAsync(ulong userId, int deleteMessageDays = 0, string reason = null)
=> this.Discord.ApiClient.CreateGuildBanAsync(this.Id, userId, deleteMessageDays, reason);
/// <summary>
/// Unbans a user from this guild.
/// </summary>
/// <param name="user">User to unban.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.BanMembers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the user does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task UnbanMemberAsync(DiscordUser user, string reason = null)
=> this.Discord.ApiClient.RemoveGuildBanAsync(this.Id, user.Id, reason);
/// <summary>
/// Unbans a user by ID.
/// </summary>
/// <param name="userId">ID of the user to unban.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.BanMembers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the user does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task UnbanMemberAsync(ulong userId, string reason = null)
=> this.Discord.ApiClient.RemoveGuildBanAsync(this.Id, userId, reason);
/// <summary>
/// Leaves this guild.
/// </summary>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task LeaveAsync()
=> this.Discord.ApiClient.LeaveGuildAsync(this.Id);
/// <summary>
/// Gets the bans for this guild, allowing for pagination.
/// </summary>
/// <param name="limit">Maximum number of bans to fetch. Max 1000. Defaults to 1000.</param>
/// <param name="before">The Id of the user before which to fetch the bans. Overrides <paramref name="after"/> if both are present.</param>
/// <param name="after">The Id of the user after which to fetch the bans.</param>
/// <returns>Collection of bans in this guild in ascending order by user id.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.BanMembers"/> permission.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<IReadOnlyList<DiscordBan>> GetBansAsync(int? limit = null, ulong? before = null, ulong? after = null)
=> this.Discord.ApiClient.GetGuildBansAsync(this.Id, limit, before, after);
/// <summary>
/// Gets a ban for a specific user.
/// </summary>
/// <param name="userId">The Id of the user to get the ban for.</param>
/// <returns>The requested ban object.</returns>
/// <exception cref="NotFoundException">Thrown when the specified user is not banned.</exception>
public Task<DiscordBan> GetBanAsync(ulong userId)
=> this.Discord.ApiClient.GetGuildBanAsync(this.Id, userId);
/// <summary>
/// Tries to get a ban for a specific user.
/// </summary>
/// <param name="userId">The Id of the user to get the ban for.</param>
/// <returns>The requested ban object or null if not found.</returns>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public async Task<DiscordBan?> TryGetBanAsync(ulong userId)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
try
{
return await this.GetBanAsync(userId).ConfigureAwait(false);
}
catch (NotFoundException)
{
return null;
}
}
/// <summary>
/// Gets a ban for a specific user.
/// </summary>
/// <param name="user">The user to get the ban for.</param>
/// <returns>The requested ban object.</returns>
/// <exception cref="NotFoundException">Thrown when the specified user is not banned.</exception>
public Task<DiscordBan> GetBanAsync(DiscordUser user)
=> this.GetBanAsync(user.Id);
/// <summary>
/// Tries to get a ban for a specific user.
/// </summary>
/// <param name="user">The user to get the ban for.</param>
/// <returns>The requested ban object or null if not found.</returns>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public async Task<DiscordBan?> TryGetBanAsync(DiscordUser user)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
try
{
return await this.GetBanAsync(user).ConfigureAwait(false);
}
catch (NotFoundException)
{
return null;
}
}
/// <summary>
/// Gets all auto mod rules for a guild.
/// </summary>
/// <returns>A collection of all rules in the guild.</returns>
public Task<ReadOnlyCollection<AutomodRule>> GetAutomodRulesAsync()
=> this.Discord.ApiClient.GetAutomodRulesAsync(this.Id);
/// <summary>
/// Gets a specific auto mod rule.
/// </summary>
/// <param name="ruleId">The rule id to get.</param>
/// <returns>The auto mod rule.</returns>
public Task<AutomodRule> GetAutomodRuleAsync(ulong ruleId)
=> this.Discord.ApiClient.GetAutomodRuleAsync(this.Id, ruleId);
/// <summary>
/// Creates a new auto mod rule in a guild.
/// </summary>
/// <param name="name">The name of the rule.</param>
/// <param name="eventType">The event type of the rule.</param>
/// <param name="triggerType">The trigger type of the rule.</param>
/// <param name="actions">The actions of the rule.</param>
/// <param name="triggerMetadata">The meta data of the rule.</param>
/// <param name="enabled">Whether this rule is enabled.</param>
/// <param name="exemptRoles">The exempt roles of the rule.</param>
/// <param name="exemptChannels">The exempt channels of the rule.</param>
/// <param name="reason">The reason for this addition</param>
/// <returns>The created rule.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageGuild"/> permission.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<AutomodRule> CreateAutomodRuleAsync(string name, AutomodEventType eventType, AutomodTriggerType triggerType, IEnumerable<AutomodAction> actions,
AutomodTriggerMetadata triggerMetadata = null, bool enabled = false, IEnumerable<ulong> exemptRoles = null, IEnumerable<ulong> exemptChannels = null, string reason = null)
=> await this.Discord.ApiClient.CreateAutomodRuleAsync(this.Id, name, eventType, triggerType, actions, triggerMetadata, enabled, exemptRoles, exemptChannels, reason);
#region Scheduled Events
/// <summary>
/// Creates a scheduled event.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="scheduledStartTime">The scheduled start time.</param>
/// <param name="scheduledEndTime">The scheduled end time.</param>
/// <param name="channel">The channel.</param>
/// <param name="metadata">The metadata.</param>
/// <param name="description">The description.</param>
/// <param name="type">The type.</param>
/// <param name="coverImage">The cover image.</param>
/// <param name="reason">The reason.</param>
/// <returns>A scheduled event.</returns>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordScheduledEvent> CreateScheduledEventAsync(string name, DateTimeOffset scheduledStartTime, DateTimeOffset? scheduledEndTime = null, DiscordChannel channel = null, DiscordScheduledEventEntityMetadata metadata = null, string description = null, ScheduledEventEntityType type = ScheduledEventEntityType.StageInstance, Optional<Stream> coverImage = default, string reason = null)
{
var coverb64 = ImageTool.Base64FromStream(coverImage);
return await this.Discord.ApiClient.CreateGuildScheduledEventAsync(this.Id, type == ScheduledEventEntityType.External ? null : channel?.Id, type == ScheduledEventEntityType.External ? metadata : null, name, scheduledStartTime, scheduledEndTime.HasValue && type == ScheduledEventEntityType.External ? scheduledEndTime.Value : null, description, type, coverb64, reason);
}
/// <summary>
/// Creates a scheduled event with type <see cref="ScheduledEventEntityType.External"/>.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="scheduledStartTime">The scheduled start time.</param>
/// <param name="scheduledEndTime">The scheduled end time.</param>
/// <param name="location">The location of the external event.</param>
/// <param name="description">The description.</param>
/// <param name="coverImage">The cover image.</param>
/// <param name="reason">The reason.</param>
/// <returns>A scheduled event.</returns>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordScheduledEvent> CreateExternalScheduledEventAsync(string name, DateTimeOffset scheduledStartTime, DateTimeOffset scheduledEndTime, string location, string description = null, Optional<Stream> coverImage = default, string reason = null)
{
var coverb64 = ImageTool.Base64FromStream(coverImage);
return await this.Discord.ApiClient.CreateGuildScheduledEventAsync(this.Id, null, new DiscordScheduledEventEntityMetadata(location), name, scheduledStartTime, scheduledEndTime, description, ScheduledEventEntityType.External, coverb64, reason);
}
/// <summary>
/// Gets a specific scheduled events.
/// </summary>
/// <param name="scheduledEventId">The Id of the event to get.</param>
/// <param name="withUserCount">Whether to include user count.</param>
/// <returns>A scheduled event.</returns>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordScheduledEvent> GetScheduledEventAsync(ulong scheduledEventId, bool? withUserCount = null)
=> this.ScheduledEventsInternal.TryGetValue(scheduledEventId, out var ev) ? ev : await this.Discord.ApiClient.GetGuildScheduledEventAsync(this.Id, scheduledEventId, withUserCount);
/// <summary>
/// Tries to get a specific scheduled events.
/// </summary>
/// <param name="scheduledEventId">The Id of the event to get.</param>
/// <param name="withUserCount">Whether to include user count.</param>
/// <returns>A scheduled event or null if not found.</returns>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public async Task<DiscordScheduledEvent?> TryGetScheduledEventAsync(ulong scheduledEventId, bool? withUserCount = null)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
try
{
return await this.GetScheduledEventAsync(scheduledEventId, withUserCount).ConfigureAwait(false);
}
catch (NotFoundException)
{
return null;
}
}
/// <summary>
/// Gets a specific scheduled events.
/// </summary>
/// <param name="scheduledEvent">The event to get.</param>
/// <param name="withUserCount">Whether to include user count.</param>
/// <returns>A scheduled event.</returns>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordScheduledEvent> GetScheduledEventAsync(DiscordScheduledEvent scheduledEvent, bool? withUserCount = null)
=> await this.GetScheduledEventAsync(scheduledEvent.Id, withUserCount);
/// <summary>
/// Tries to get a specific scheduled events.
/// </summary>
/// <param name="scheduledEvent">The event to get.</param>
/// <param name="withUserCount">Whether to include user count.</param>
/// <returns>A scheduled event or null if not found.</returns>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public async Task<DiscordScheduledEvent?> TryGetScheduledEventAsync(DiscordScheduledEvent scheduledEvent, bool? withUserCount = null)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
try
{
return await this.GetScheduledEventAsync(scheduledEvent, withUserCount).ConfigureAwait(false);
}
catch (NotFoundException)
{
return null;
}
}
/// <summary>
/// Gets the guilds scheduled events.
/// </summary>
/// <param name="withUserCount">Whether to include user count.</param>
/// <returns>A list of the guilds scheduled events.</returns>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<IReadOnlyDictionary<ulong, DiscordScheduledEvent>> GetScheduledEventsAsync(bool? withUserCount = null)
=> await this.Discord.ApiClient.ListGuildScheduledEventsAsync(this.Id, withUserCount);
#endregion
/// <summary>
/// Creates a new text channel in this guild.
/// </summary>
/// <param name="name">Name of the new channel.</param>
/// <param name="parent">Category to put this channel in.</param>
/// <param name="topic">Topic of the channel.</param>
/// <param name="overwrites">Permission overwrites for this channel.</param>
/// <param name="nsfw">Whether the channel is to be flagged as not safe for work.</param>
/// <param name="perUserRateLimit">Slow mode timeout for users.</param>
/// <param name="defaultAutoArchiveDuration">The default auto archive duration for new threads.</param>
/// <param name="flags">The flags of the new channel.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <returns>The newly-created channel.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageChannels"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordChannel> CreateTextChannelAsync(string name, DiscordChannel parent = null, Optional<string> topic = default, IEnumerable<DiscordOverwriteBuilder> overwrites = null, bool? nsfw = null, Optional<int?> perUserRateLimit = default, ThreadAutoArchiveDuration defaultAutoArchiveDuration = ThreadAutoArchiveDuration.OneDay, Optional<ChannelFlags?> flags = default, string reason = null)
=> this.CreateChannelAsync(name, ChannelType.Text, parent, topic, null, null, overwrites, nsfw, perUserRateLimit, null, defaultAutoArchiveDuration, flags, reason);
/// <summary>
/// Creates a new forum channel in this guild.
/// <note type="note">The field template is not yet released, so it won't applied.</note>
/// </summary>
/// <param name="name">Name of the new channel.</param>
/// <param name="parent">Category to put this channel in.</param>
/// <param name="topic">Topic of the channel.</param>
/// <param name="overwrites">Permission overwrites for this channel.</param>
/// <param name="nsfw">Whether the channel is to be flagged as not safe for work.</param>
/// <param name="defaultReactionEmoji">The default reaction emoji for posts.</param>
/// <param name="perUserRateLimit">Slow mode timeout for users.</param>
/// <param name="postCreateUserRateLimit">Slow mode timeout for user post creations.</param>
/// <param name="defaultAutoArchiveDuration">The default auto archive duration for new threads.</param>
/// <param name="defaultSortOrder">The default sort order for posts in the new channel.</param>
/// <param name="flags">The flags of the new channel.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <returns>The newly-created channel.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageChannels"/> permission or the guild does not have the forum channel feature.</exception>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordChannel> CreateForumChannelAsync(string name, DiscordChannel parent = null, Optional<string> topic = default, IEnumerable<DiscordOverwriteBuilder> overwrites = null, bool? nsfw = null, Optional<ForumReactionEmoji> defaultReactionEmoji = default, Optional<int?> perUserRateLimit = default, Optional<int?> postCreateUserRateLimit = default, ThreadAutoArchiveDuration defaultAutoArchiveDuration = ThreadAutoArchiveDuration.OneDay, Optional<ForumPostSortOrder> defaultSortOrder = default, Optional<ChannelFlags?> flags = default, string reason = null)
=> this.Discord.ApiClient.CreateForumChannelAsync(this.Id, name, parent?.Id, topic, null, nsfw, defaultReactionEmoji, perUserRateLimit, postCreateUserRateLimit, defaultSortOrder, defaultAutoArchiveDuration, overwrites, flags, reason);
/// <summary>
/// Creates a new channel category in this guild.
/// </summary>
/// <param name="name">Name of the new category.</param>
/// <param name="overwrites">Permission overwrites for this category.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <returns>The newly-created channel category.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageChannels"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordChannel> CreateChannelCategoryAsync(string name, IEnumerable<DiscordOverwriteBuilder> overwrites = null, string reason = null)
=> this.CreateChannelAsync(name, ChannelType.Category, null, Optional.None, null, null, overwrites, null, Optional.None, null, null, Optional.None, reason);
/// <summary>
/// Creates a new stage channel in this guild.
/// </summary>
/// <param name="name">Name of the new stage channel.</param>
/// <param name="overwrites">Permission overwrites for this stage channel.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <returns>The newly-created stage channel.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageChannels"/>.</exception>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
/// <exception cref="NotSupportedException">Thrown when the guilds has not enabled community.</exception>
public Task<DiscordChannel> CreateStageChannelAsync(string name, IEnumerable<DiscordOverwriteBuilder> overwrites = null, string reason = null)
=> this.Features.HasFeature(GuildFeaturesEnum.HasCommunityEnabled) ? this.CreateChannelAsync(name, ChannelType.Stage, null, Optional.None, null, null, overwrites, null, Optional.None, null, null, Optional.None, reason) : throw new NotSupportedException("Guild has not enabled community. Can not create a stage channel.");
/// <summary>
/// Creates a new news channel in this guild.
/// </summary>
/// <param name="name">Name of the new news channel.</param>
/// <param name="overwrites">Permission overwrites for this news channel.</param>
/// <param name="defaultAutoArchiveDuration">The default auto archive duration for new threads.</param>
/// <param name="flags">The flags of the new channel.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <returns>The newly-created news channel.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageChannels"/>.</exception>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
/// <exception cref="NotSupportedException">Thrown when the guilds has not enabled community.</exception>
public Task<DiscordChannel> CreateNewsChannelAsync(string name, IEnumerable<DiscordOverwriteBuilder> overwrites = null, string reason = null, ThreadAutoArchiveDuration defaultAutoArchiveDuration = ThreadAutoArchiveDuration.OneDay, Optional<ChannelFlags?> flags = default)
=> this.Features.HasFeature(GuildFeaturesEnum.HasCommunityEnabled) ? this.CreateChannelAsync(name, ChannelType.News, null, Optional.None, null, null, overwrites, null, Optional.None, null, defaultAutoArchiveDuration, flags, reason) : throw new NotSupportedException("Guild has not enabled community. Can not create a news channel.");
/// <summary>
/// Creates a new voice channel in this guild.
/// </summary>
/// <param name="name">Name of the new channel.</param>
/// <param name="parent">Category to put this channel in.</param>
/// <param name="bitrate">Bitrate of the channel.</param>
/// <param name="userLimit">Maximum number of users in the channel.</param>
/// <param name="overwrites">Permission overwrites for this channel.</param>
/// <param name="flags">The flags of the new channel.</param>
/// <param name="qualityMode">Video quality mode of the channel.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <returns>The newly-created channel.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageChannels"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordChannel> CreateVoiceChannelAsync(string name, DiscordChannel parent = null, int? bitrate = null, int? userLimit = null, IEnumerable<DiscordOverwriteBuilder> overwrites = null, VideoQualityMode? qualityMode = null, Optional<ChannelFlags?> flags = default, string reason = null)
=> this.CreateChannelAsync(name, ChannelType.Voice, parent, Optional.None, bitrate, userLimit, overwrites, null, Optional.None, qualityMode, null, flags, reason);
/// <summary>
/// Creates a new channel in this guild.
/// </summary>
/// <param name="name">Name of the new channel.</param>
/// <param name="type">Type of the new channel.</param>
/// <param name="parent">Category to put this channel in.</param>
/// <param name="topic">Topic of the channel.</param>
/// <param name="bitrate">Bitrate of the channel. Applies to voice only.</param>
/// <param name="userLimit">Maximum number of users in the channel. Applies to voice only.</param>
/// <param name="overwrites">Permission overwrites for this channel.</param>
/// <param name="nsfw">Whether the channel is to be flagged as not safe for work. Applies to text only.</param>
/// <param name="perUserRateLimit">Slow mode timeout for users.</param>
/// <param name="qualityMode">Video quality mode of the channel. Applies to voice only.</param>
/// <param name="defaultAutoArchiveDuration">The default auto archive duration for new threads.</param>
/// <param name="flags">The flags of the new channel.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <returns>The newly-created channel.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageChannels"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordChannel> CreateChannelAsync(string name, ChannelType type, DiscordChannel parent = null, Optional<string> topic = default, int? bitrate = null, int? userLimit = null, IEnumerable<DiscordOverwriteBuilder> overwrites = null, bool? nsfw = null, Optional<int?> perUserRateLimit = default, VideoQualityMode? qualityMode = null, ThreadAutoArchiveDuration? defaultAutoArchiveDuration = null, Optional<ChannelFlags?> flags = default, string reason = null) =>
// technically you can create news/store channels but not always
type != ChannelType.Text && type != ChannelType.Voice && type != ChannelType.Category && type != ChannelType.News && type != ChannelType.Store && type != ChannelType.Stage
? throw new ArgumentException("Channel type must be text, voice, stage, or category.", nameof(type))
: type == ChannelType.Category && parent != null
? throw new ArgumentException("Cannot specify parent of a channel category.", nameof(parent))
: this.Discord.ApiClient.CreateGuildChannelAsync(this.Id, name, type, parent?.Id, topic, bitrate, userLimit, overwrites, nsfw, perUserRateLimit, qualityMode, defaultAutoArchiveDuration, flags, reason);
/// <summary>
/// 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.
/// </summary>
/// <exception cref="NotFoundException">Thrown when the thread does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordThreadResult> GetActiveThreadsAsync()
=> this.Discord.ApiClient.GetActiveThreadsAsync(this.Id);
/// <summary>
/// <para>Deletes all channels in this guild.</para>
/// <para>Note that this is irreversible. Use carefully!</para>
/// </summary>
/// <returns></returns>
public Task DeleteAllChannelsAsync()
{
var tasks = this.Channels.Values.Select(xc => xc.DeleteAsync());
return Task.WhenAll(tasks);
}
/// <summary>
/// Estimates the number of users to be pruned.
/// </summary>
/// <param name="days">Minimum number of inactivity days required for users to be pruned. Defaults to 7.</param>
/// <param name="includedRoles">The roles to be included in the prune.</param>
/// <returns>Number of users that will be pruned.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.KickMembers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<int> GetPruneCountAsync(int days = 7, IEnumerable<DiscordRole> includedRoles = null)
{
if (includedRoles != null)
{
includedRoles = includedRoles.Where(r => r != null);
var rawRoleIds = includedRoles
.Where(x => this.RolesInternal.ContainsKey(x.Id))
.Select(x => x.Id);
return this.Discord.ApiClient.GetGuildPruneCountAsync(this.Id, days, rawRoleIds);
}
return this.Discord.ApiClient.GetGuildPruneCountAsync(this.Id, days, null);
}
/// <summary>
/// Prunes inactive users from this guild.
/// </summary>
/// <param name="days">Minimum number of inactivity days required for users to be pruned. Defaults to 7.</param>
/// <param name="computePruneCount">Whether to return the prune count after this method completes. This is discouraged for larger guilds.</param>
/// <param name="includedRoles">The roles to be included in the prune.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <returns>Number of users pruned.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageChannels"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<int?> PruneAsync(int days = 7, bool computePruneCount = true, IEnumerable<DiscordRole> includedRoles = null, string reason = null)
{
if (includedRoles != null)
{
includedRoles = includedRoles.Where(r => r != null);
var rawRoleIds = includedRoles
.Where(x => this.RolesInternal.ContainsKey(x.Id))
.Select(x => x.Id);
return this.Discord.ApiClient.BeginGuildPruneAsync(this.Id, days, computePruneCount, rawRoleIds, reason);
}
return this.Discord.ApiClient.BeginGuildPruneAsync(this.Id, days, computePruneCount, null, reason);
}
/// <summary>
/// Gets integrations attached to this guild.
/// </summary>
/// <returns>Collection of integrations attached to this guild.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageGuild"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<IReadOnlyList<DiscordIntegration>> GetIntegrationsAsync()
=> this.Discord.ApiClient.GetGuildIntegrationsAsync(this.Id);
/// <summary>
/// Attaches an integration from current user to this guild.
/// </summary>
/// <param name="integration">Integration to attach.</param>
/// <returns>The integration after being attached to the guild.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageGuild"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordIntegration> AttachUserIntegrationAsync(DiscordIntegration integration)
=> this.Discord.ApiClient.CreateGuildIntegrationAsync(this.Id, integration.Type, integration.Id);
/// <summary>
/// Modifies an integration in this guild.
/// </summary>
/// <param name="integration">Integration to modify.</param>
/// <param name="expireBehaviour">Number of days after which the integration expires.</param>
/// <param name="expireGracePeriod">Length of grace period which allows for renewing the integration.</param>
/// <param name="enableEmoticons">Whether emotes should be synced from this integration.</param>
/// <returns>The modified integration.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageGuild"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordIntegration> ModifyIntegrationAsync(DiscordIntegration integration, int expireBehaviour, int expireGracePeriod, bool enableEmoticons)
=> this.Discord.ApiClient.ModifyGuildIntegrationAsync(this.Id, integration.Id, expireBehaviour, expireGracePeriod, enableEmoticons);
/// <summary>
/// Removes an integration from this guild.
/// </summary>
/// <param name="integration">Integration to remove.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageGuild"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task DeleteIntegrationAsync(DiscordIntegration integration)
=> this.Discord.ApiClient.DeleteGuildIntegrationAsync(this.Id, integration);
/// <summary>
/// Forces re-synchronization of an integration for this guild.
/// </summary>
/// <param name="integration">Integration to synchronize.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageGuild"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the guild does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task SyncIntegrationAsync(DiscordIntegration integration)
=> this.Discord.ApiClient.SyncGuildIntegrationAsync(this.Id, integration.Id);
/// <summary>
/// Gets the voice regions for this guild.
/// </summary>
/// <returns>Voice regions available for this guild.</returns>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<IReadOnlyList<DiscordVoiceRegion>> 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;
}
/// <summary>
/// Gets an invite from this guild from an invite code.
/// </summary>
/// <param name="code">The invite code</param>
/// <returns>An invite, or null if not in cache.</returns>
public DiscordInvite GetInvite(string code)
=> this.Invites.TryGetValue(code, out var invite) ? invite : null;
/// <summary>
/// Gets all the invites created for all the channels in this guild.
/// </summary>
/// <returns>A collection of invites.</returns>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<IReadOnlyList<DiscordInvite>> GetInvitesAsync()
{
var res = await this.Discord.ApiClient.GetGuildInvitesAsync(this.Id).ConfigureAwait(false);
var intents = this.Discord.Configuration.Intents;
if (!intents.HasIntent(DiscordIntents.GuildInvites))
{
foreach (var r in res)
this.Invites[r.Code] = r;
}
return res;
}
/// <summary>
/// Gets the vanity invite for this guild.
/// </summary>
/// <returns>A partial vanity invite.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageGuild"/> permission.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordInvite> GetVanityInviteAsync()
=> this.Discord.ApiClient.GetGuildVanityUrlAsync(this.Id);
/// <summary>
/// Gets all the webhooks created for all the channels in this guild.
/// </summary>
/// <returns>A collection of webhooks this guild has.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageWebhooks"/> permission.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<IReadOnlyList<DiscordWebhook>> GetWebhooksAsync()
=> this.Discord.ApiClient.GetGuildWebhooksAsync(this.Id);
/// <summary>
/// Gets this guild's widget image.
/// </summary>
/// <param name="bannerType">The format of the widget.</param>
/// <returns>The URL of the widget image.</returns>
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}";
}
/// <summary>
/// Gets a member of this guild by their user ID.
/// </summary>
/// <param name="userId">ID of the member to get.</param>
/// <param name="fetch">Whether to fetch the member from the api prior to cache.</param>
/// <returns>The requested member.</returns>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordMember> GetMemberAsync(ulong userId, bool fetch = false)
{
if (!fetch && this.MembersInternal != null && this.MembersInternal.TryGetValue(userId, out var mbr))
return mbr;
mbr = await this.Discord.ApiClient.GetGuildMemberAsync(this.Id, userId).ConfigureAwait(false);
var intents = this.Discord.Configuration.Intents;
if (intents.HasIntent(DiscordIntents.GuildMembers))
{
if (this.MembersInternal != null)
{
this.MembersInternal[userId] = mbr;
}
}
return mbr;
}
/// <summary>
/// Retrieves a full list of members from Discord. This method will bypass cache.
/// </summary>
/// <returns>A collection of all members in this guild.</returns>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<IReadOnlyCollection<DiscordMember>> GetAllMembersAsync()
{
var recmbr = new HashSet<DiscordMember>();
var recd = 1000;
var last = 0ul;
while (recd > 0)
{
var tms = await this.Discord.ApiClient.ListGuildMembersAsync(this.Id, 1000, last == 0 ? null : last).ConfigureAwait(false);
recd = tms.Count;
foreach (var xtm in tms)
{
var usr = new DiscordUser(xtm.User) { Discord = this.Discord };
usr = this.Discord.UserCache.AddOrUpdate(xtm.User.Id, usr, (id, old) =>
{
old.Username = usr.Username;
old.Discord = usr.Discord;
old.AvatarHash = usr.AvatarHash;
return old;
});
recmbr.Add(new DiscordMember(xtm) { Discord = this.Discord, GuildId = this.Id });
}
var tm = tms.LastOrDefault();
last = tm?.User.Id ?? 0;
}
return new ReadOnlySet<DiscordMember>(recmbr);
}
/// <summary>
/// Requests that Discord send a list of guild members based on the specified arguments. This method will fire the <see cref="DiscordClient.GuildMembersChunked"/> event.
/// <para>If no arguments aside from <paramref name="presences"/> and <paramref name="nonce"/> are specified, this will request all guild members.</para>
/// </summary>
/// <param name="query">Filters the returned members based on what the username starts with. Either this or <paramref name="userIds"/> must not be null.
/// The <paramref name="limit"/> must also be greater than 0 if this is specified.</param>
/// <param name="limit">Total number of members to request. This must be greater than 0 if <paramref name="query"/> is specified.</param>
/// <param name="presences">Whether to include the <see cref="DisCatSharp.EventArgs.GuildMembersChunkEventArgs.Presences"/> associated with the fetched members.</param>
/// <param name="userIds">Whether to limit the request to the specified user ids. Either this or <paramref name="query"/> must not be null.</param>
/// <param name="nonce">The unique string to identify the response.</param>
public async Task RequestMembersAsync(string query = "", int limit = 0, bool? presences = null, IEnumerable<ulong> 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);
}
/// <summary>
/// Gets all the channels this guild has.
/// </summary>
/// <returns>A collection of this guild's channels.</returns>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<IReadOnlyList<DiscordChannel>> GetChannelsAsync()
=> this.Discord.ApiClient.GetGuildChannelsAsync(this.Id);
/// <summary>
/// Creates a new role in this guild.
/// </summary>
/// <param name="name">Name of the role.</param>
/// <param name="permissions">Permissions for the role.</param>
/// <param name="color">Color for the role.</param>
/// <param name="hoist">Whether the role is to be hoisted.</param>
/// <param name="mentionable">Whether the role is to be mentionable.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <returns>The newly-created role.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageRoles"/> permission.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordRole> 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);
/// <summary>
/// Gets a role from this guild by its ID.
/// </summary>
/// <param name="id">ID of the role to get.</param>
/// <returns>Requested role.</returns>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public DiscordRole GetRole(ulong id)
=> this.RolesInternal.TryGetValue(id, out var role) ? role : null;
/// <summary>
/// Gets a channel from this guild by its ID.
/// </summary>
/// <param name="id">ID of the channel to get.</param>
/// <returns>Requested channel.</returns>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public DiscordChannel GetChannel(ulong id)
=> this.ChannelsInternal != null && this.ChannelsInternal.TryGetValue(id, out var channel) ? channel : null;
/// <summary>
/// Gets a thread from this guild by its ID.
/// </summary>
/// <param name="id">ID of the thread to get.</param>
/// <returns>Requested thread.</returns>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public DiscordThreadChannel GetThread(ulong id)
=> this.ThreadsInternal != null && this.ThreadsInternal.TryGetValue(id, out var thread) ? thread : null;
/// <summary>
/// Gets all of this guild's custom emojis.
/// </summary>
/// <returns>All of this guild's custom emojis.</returns>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<IReadOnlyList<DiscordGuildEmoji>> GetEmojisAsync()
=> this.Discord.ApiClient.GetGuildEmojisAsync(this.Id);
/// <summary>
/// Gets this guild's specified custom emoji.
/// </summary>
/// <param name="id">ID of the emoji to get.</param>
/// <returns>The requested custom emoji.</returns>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordGuildEmoji> GetEmojiAsync(ulong id)
=> this.Discord.ApiClient.GetGuildEmojiAsync(this.Id, id);
/// <summary>
/// Creates a new custom emoji for this guild.
/// </summary>
/// <param name="name">Name of the new emoji.</param>
/// <param name="image">Image to use as the emoji.</param>
/// <param name="roles">Roles for which the emoji will be available. This works only if your application is whitelisted as integration.</param>
/// <param name="reason">Reason for audit log.</param>
/// <returns>The newly-created emoji.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageEmojisAndStickers"/> permission.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordGuildEmoji> CreateEmojiAsync(string name, Stream image, IEnumerable<DiscordRole> roles = null, string reason = null)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullException(nameof(name));
name = name.Trim();
if (name.Length < 2 || name.Length > 50)
throw new ArgumentException("Emoji name needs to be between 2 and 50 characters long.");
if (image == null)
throw new ArgumentNullException(nameof(image));
var image64 = ImageTool.Base64FromStream(image);
return this.Discord.ApiClient.CreateGuildEmojiAsync(this.Id, name, image64, roles?.Select(xr => xr.Id), reason);
}
/// <summary>
/// Modifies a this guild's custom emoji.
/// </summary>
/// <param name="emoji">Emoji to modify.</param>
/// <param name="name">New name for the emoji.</param>
/// <param name="roles">Roles for which the emoji will be available. This works only if your application is whitelisted as integration.</param>
/// <param name="reason">Reason for audit log.</param>
/// <returns>The modified emoji.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageEmojisAndStickers"/> permission.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordGuildEmoji> ModifyEmojiAsync(DiscordGuildEmoji emoji, string name, IEnumerable<DiscordRole> 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);
}
/// <summary>
/// Deletes this guild's custom emoji.
/// </summary>
/// <param name="emoji">Emoji to delete.</param>
/// <param name="reason">Reason for audit log.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageEmojisAndStickers"/> permission.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task DeleteEmojiAsync(DiscordGuildEmoji emoji, string reason = null) =>
emoji == null
? throw new ArgumentNullException(nameof(emoji))
: emoji.Guild.Id != this.Id
? throw new ArgumentException("This emoji does not belong to this guild.")
: this.Discord.ApiClient.DeleteGuildEmojiAsync(this.Id, emoji.Id, reason);
/// <summary>
/// Gets all of this guild's custom stickers.
/// </summary>
/// <returns>All of this guild's custom stickers.</returns>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<IReadOnlyList<DiscordSticker>> GetStickersAsync()
{
var stickers = await this.Discord.ApiClient.GetGuildStickersAsync(this.Id);
foreach (var xstr in stickers)
{
this.StickersInternal.AddOrUpdate(xstr.Id, xstr, (id, old) =>
{
old.Name = xstr.Name;
old.Description = xstr.Description;
old.InternalTags = xstr.InternalTags;
return old;
});
}
return stickers;
}
/// <summary>
/// Gets a sticker
/// </summary>
/// <exception cref="UnauthorizedException">Thrown when the sticker could not be found.</exception>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageEmojisAndStickers"/> permission.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
/// <exception cref="ArgumentException">Sticker does not belong to a guild.</exception>
public Task<DiscordSticker> GetStickerAsync(ulong stickerId)
=> this.Discord.ApiClient.GetGuildStickerAsync(this.Id, stickerId);
/// <summary>
/// Creates a sticker
/// </summary>
/// <param name="name">The name of the sticker.</param>
/// <param name="description">The optional description of the sticker.</param>
/// <param name="emoji">The emoji to associate the sticker with.</param>
/// <param name="format">The file format the sticker is written in.</param>
/// <param name="file">The sticker.</param>
/// <param name="reason">Audit log reason</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageEmojisAndStickers"/> permission.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordSticker> 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.GIF => "gif",
StickerFormat.Lottie => "json",
_ => throw new InvalidOperationException("This format is not supported.")
};
var contentType = format switch
{
StickerFormat.Png => "image/png",
StickerFormat.Apng => "image/png",
StickerFormat.GIF => "image/gif",
StickerFormat.Lottie => "application/json",
_ => throw new InvalidOperationException("This format is not supported.")
};
return emoji.Id is not 0
? throw new InvalidOperationException("Only unicode emoji can be used for stickers.")
: name.Length < 2 || name.Length > 30
? throw new ArgumentOutOfRangeException(nameof(name), "Sticker name needs to be between 2 and 30 characters long.")
: description.Length < 1 || description.Length > 100
? throw new ArgumentOutOfRangeException(nameof(description), "Sticker description needs to be between 1 and 100 characters long.")
: this.Discord.ApiClient.CreateGuildStickerAsync(this.Id, name, description, emoji.GetDiscordName().Replace(":", ""), new DiscordMessageFile("sticker", file, null, fileExt, contentType), reason);
}
/// <summary>
/// Modifies a sticker
/// </summary>
/// <param name="sticker">The id of the sticker to modify</param>
/// <param name="name">The name of the sticker</param>
/// <param name="description">The description of the sticker</param>
/// <param name="emoji">The emoji to associate with this sticker.</param>
/// <param name="reason">Audit log reason</param>
/// <returns>A sticker object</returns>
/// <exception cref="UnauthorizedException">Thrown when the sticker could not be found.</exception>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageEmojisAndStickers"/> permission.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
/// <exception cref="ArgumentException">Sticker does not belong to a guild.</exception>
public async Task<DiscordSticker> ModifyStickerAsync(ulong sticker, Optional<string> name, Optional<string> description, Optional<DiscordEmoji> emoji, string reason = null)
{
if (!this.StickersInternal.TryGetValue(sticker, out var stickerobj) || stickerobj.Guild.Id != this.Id)
throw new ArgumentException("This sticker does not belong to this guild.");
if (name.HasValue && (name.Value.Length < 2 || name.Value.Length > 30))
throw new ArgumentException("Sticker name needs to be between 2 and 30 characters long.");
if (description.HasValue && (description.Value.Length < 1 || description.Value.Length > 100))
throw new ArgumentException("Sticker description needs to be between 1 and 100 characters long.");
if (emoji.HasValue && emoji.Value.Id > 0)
throw new ArgumentException("Only unicode emojis can be used with stickers.");
string uemoji = null;
if (emoji.HasValue)
uemoji = emoji.Value.GetDiscordName().Replace(":", "");
var usticker = await this.Discord.ApiClient.ModifyGuildStickerAsync(this.Id, sticker, name, description, uemoji, reason).ConfigureAwait(false);
if (this.StickersInternal.TryGetValue(usticker.Id, out var old))
this.StickersInternal.TryUpdate(usticker.Id, usticker, old);
return usticker;
}
/// <summary>
/// Modifies a sticker
/// </summary>
/// <param name="sticker">The sticker to modify</param>
/// <param name="name">The name of the sticker</param>
/// <param name="description">The description of the sticker</param>
/// <param name="emoji">The emoji to associate with this sticker.</param>
/// <param name="reason">Audit log reason</param>
/// <returns>A sticker object</returns>
/// <exception cref="UnauthorizedException">Thrown when the sticker could not be found.</exception>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageEmojisAndStickers"/> permission.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
/// <exception cref="ArgumentException">Sticker does not belong to a guild.</exception>
public Task<DiscordSticker> ModifyStickerAsync(DiscordSticker sticker, Optional<string> name, Optional<string> description, Optional<DiscordEmoji> emoji, string reason = null)
=> this.ModifyStickerAsync(sticker.Id, name, description, emoji, reason);
/// <summary>
/// Deletes a sticker
/// </summary>
/// <param name="sticker">Id of sticker to delete</param>
/// <param name="reason">Audit log reason</param>
/// <exception cref="UnauthorizedException">Thrown when the sticker could not be found.</exception>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageEmojisAndStickers"/> permission.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
/// <exception cref="ArgumentException">Sticker does not belong to a guild.</exception>
public Task DeleteStickerAsync(ulong sticker, string reason = null) =>
!this.StickersInternal.TryGetValue(sticker, out var stickerobj)
? throw new ArgumentNullException(nameof(sticker))
: stickerobj.Guild.Id != this.Id
? throw new ArgumentException("This sticker does not belong to this guild.")
: this.Discord.ApiClient.DeleteGuildStickerAsync(this.Id, sticker, reason);
/// <summary>
/// Deletes a sticker
/// </summary>
/// <param name="sticker">Sticker to delete</param>
/// <param name="reason">Audit log reason</param>
/// <exception cref="UnauthorizedException">Thrown when the sticker could not be found.</exception>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageEmojisAndStickers"/> permission.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
/// <exception cref="ArgumentException">Sticker does not belong to a guild.</exception>
public Task DeleteStickerAsync(DiscordSticker sticker, string reason = null)
=> this.DeleteStickerAsync(sticker.Id, reason);
/// <summary>
/// <para>Gets the default channel for this guild.</para>
/// <para>Default channel is the first channel current member can see.</para>
/// </summary>
/// <returns>This member's default guild.</returns>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public DiscordChannel GetDefaultChannel() =>
this.ChannelsInternal?.Values.Where(xc => xc.Type == ChannelType.Text)
.OrderBy(xc => xc.Position)
.FirstOrDefault(xc => (xc.PermissionsFor(this.CurrentMember) & DisCatSharp.Enums.Permissions.AccessChannels) == DisCatSharp.Enums.Permissions.AccessChannels);
/// <summary>
/// Gets the guild's widget
/// </summary>
/// <returns>The guild's widget</returns>
public Task<DiscordWidget> GetWidgetAsync()
=> this.Discord.ApiClient.GetGuildWidgetAsync(this.Id);
/// <summary>
/// Gets the guild's widget settings
/// </summary>
/// <returns>The guild's widget settings</returns>
public Task<DiscordWidgetSettings> GetWidgetSettingsAsync()
=> this.Discord.ApiClient.GetGuildWidgetSettingsAsync(this.Id);
/// <summary>
/// Modifies the guild's widget settings
/// </summary>
/// <param name="isEnabled">If the widget is enabled or not</param>
/// <param name="channel">Widget channel</param>
/// <param name="reason">Reason the widget settings were modified</param>
/// <returns>The newly modified widget settings</returns>
public Task<DiscordWidgetSettings> ModifyWidgetSettingsAsync(bool? isEnabled = null, DiscordChannel channel = null, string reason = null)
=> this.Discord.ApiClient.ModifyGuildWidgetSettingsAsync(this.Id, isEnabled, channel?.Id, reason);
/// <summary>
/// Gets all of this guild's templates.
/// </summary>
/// <returns>All of the guild's templates.</returns>
/// <exception cref="UnauthorizedException">Throws when the client does not have the <see cref="Permissions.ManageGuild"/> permission.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<IReadOnlyList<DiscordGuildTemplate>> GetTemplatesAsync()
=> this.Discord.ApiClient.GetGuildTemplatesAsync(this.Id);
/// <summary>
/// Creates a guild template.
/// </summary>
/// <param name="name">Name of the template.</param>
/// <param name="description">Description of the template.</param>
/// <returns>The template created.</returns>
/// <exception cref="BadRequestException">Throws when a template already exists for the guild or a null parameter is provided for the name.</exception>
/// <exception cref="UnauthorizedException">Throws when the client does not have the <see cref="Permissions.ManageGuild"/> permission.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordGuildTemplate> CreateTemplateAsync(string name, string description = null)
=> this.Discord.ApiClient.CreateGuildTemplateAsync(this.Id, name, description);
/// <summary>
/// Syncs the template to the current guild's state.
/// </summary>
/// <param name="code">The code of the template to sync.</param>
/// <returns>The template synced.</returns>
/// <exception cref="NotFoundException">Throws when the template for the code cannot be found</exception>
/// <exception cref="UnauthorizedException">Throws when the client does not have the <see cref="Permissions.ManageGuild"/> permission.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordGuildTemplate> SyncTemplateAsync(string code)
=> this.Discord.ApiClient.SyncGuildTemplateAsync(this.Id, code);
/// <summary>
/// Modifies the template's metadata.
/// </summary>
/// <param name="code">The template's code.</param>
/// <param name="name">Name of the template.</param>
/// <param name="description">Description of the template.</param>
/// <returns>The template modified.</returns>
/// <exception cref="NotFoundException">Throws when the template for the code cannot be found</exception>
/// <exception cref="UnauthorizedException">Throws when the client does not have the <see cref="Permissions.ManageGuild"/> permission.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordGuildTemplate> ModifyTemplateAsync(string code, string name = null, string description = null)
=> this.Discord.ApiClient.ModifyGuildTemplateAsync(this.Id, code, name, description);
/// <summary>
/// Deletes the template.
/// </summary>
/// <param name="code">The code of the template to delete.</param>
/// <returns>The deleted template.</returns>
/// <exception cref="NotFoundException">Throws when the template for the code cannot be found</exception>
/// <exception cref="UnauthorizedException">Throws when the client does not have the <see cref="Permissions.ManageGuild"/> permission.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordGuildTemplate> DeleteTemplateAsync(string code)
=> this.Discord.ApiClient.DeleteGuildTemplateAsync(this.Id, code);
/// <summary>
/// Gets this guild's membership screening form.
/// </summary>
/// <returns>This guild's membership screening form.</returns>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordGuildMembershipScreening> GetMembershipScreeningFormAsync()
=> this.Discord.ApiClient.GetGuildMembershipScreeningFormAsync(this.Id);
/// <summary>
/// Modifies this guild's membership screening form.
/// </summary>
/// <param name="action">Action to perform</param>
/// <returns>The modified screening form.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client doesn't have the <see cref="Permissions.ManageGuild"/> permission, or community is not enabled on this guild.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordGuildMembershipScreening> ModifyMembershipScreeningFormAsync(Action<MembershipScreeningEditModel> action)
{
var mdl = new MembershipScreeningEditModel();
action(mdl);
return await this.Discord.ApiClient.ModifyGuildMembershipScreeningFormAsync(this.Id, mdl.Enabled, mdl.Fields, mdl.Description);
}
/// <summary>
/// Gets all the application commands in this guild.
/// </summary>
/// <returns>A list of application commands in this guild.</returns>
public Task<IReadOnlyList<DiscordApplicationCommand>> GetApplicationCommandsAsync() =>
this.Discord.ApiClient.GetGuildApplicationCommandsAsync(this.Discord.CurrentApplication.Id, this.Id);
/// <summary>
/// Overwrites the existing application commands in this guild. New commands are automatically created and missing commands are automatically delete
/// </summary>
/// <param name="commands">The list of commands to overwrite with.</param>
/// <returns>The list of guild commands</returns>
public Task<IReadOnlyList<DiscordApplicationCommand>> BulkOverwriteApplicationCommandsAsync(IEnumerable<DiscordApplicationCommand> commands) =>
this.Discord.ApiClient.BulkOverwriteGuildApplicationCommandsAsync(this.Discord.CurrentApplication.Id, this.Id, commands);
/// <summary>
/// Creates or overwrites a application command in this guild.
/// </summary>
/// <param name="command">The command to create.</param>
/// <returns>The created command.</returns>
public Task<DiscordApplicationCommand> CreateApplicationCommandAsync(DiscordApplicationCommand command) =>
this.Discord.ApiClient.CreateGuildApplicationCommandAsync(this.Discord.CurrentApplication.Id, this.Id, command);
/// <summary>
/// Edits a application command in this guild.
/// </summary>
/// <param name="commandId">The id of the command to edit.</param>
/// <param name="action">Action to perform.</param>
/// <returns>The edit command.</returns>
public async Task<DiscordApplicationCommand> EditApplicationCommandAsync(ulong commandId, Action<ApplicationCommandEditModel> action)
{
var mdl = new ApplicationCommandEditModel();
action(mdl);
return await this.Discord.ApiClient.EditGuildApplicationCommandAsync(this.Discord.CurrentApplication.Id, this.Id, commandId, mdl.Name, mdl.Description, mdl.Options, mdl.NameLocalizations, mdl.DescriptionLocalizations, mdl.DefaultMemberPermissions, mdl.DmPermission, mdl.IsNsfw).ConfigureAwait(false);
}
/// <summary>
/// Gets this guild's welcome screen.
/// </summary>
/// <returns>This guild's welcome screen object.</returns>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordGuildWelcomeScreen> GetWelcomeScreenAsync() =>
this.Discord.ApiClient.GetGuildWelcomeScreenAsync(this.Id);
/// <summary>
/// Modifies this guild's welcome screen.
/// </summary>
/// <param name="action">Action to perform.</param>
/// <returns>The modified welcome screen.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client doesn't have the <see cref="Permissions.ManageGuild"/> permission, or community is not enabled on this guild.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordGuildWelcomeScreen> ModifyWelcomeScreenAsync(Action<WelcomeScreenEditModel> action)
{
var mdl = new WelcomeScreenEditModel();
action(mdl);
return await this.Discord.ApiClient.ModifyGuildWelcomeScreenAsync(this.Id, mdl.Enabled, mdl.WelcomeChannels, mdl.Description).ConfigureAwait(false);
}
#endregion
/// <summary>
/// Returns a string representation of this guild.
/// </summary>
/// <returns>String representation of this guild.</returns>
public override string ToString()
=> $"Guild {this.Id}; {this.Name}";
/// <summary>
/// Checks whether this <see cref="DiscordGuild"/> is equal to another object.
/// </summary>
/// <param name="obj">Object to compare to.</param>
/// <returns>Whether the object is equal to this <see cref="DiscordGuild"/>.</returns>
public override bool Equals(object obj)
=> this.Equals(obj as DiscordGuild);
/// <summary>
/// Checks whether this <see cref="DiscordGuild"/> is equal to another <see cref="DiscordGuild"/>.
/// </summary>
/// <param name="e"><see cref="DiscordGuild"/> to compare to.</param>
/// <returns>Whether the <see cref="DiscordGuild"/> is equal to this <see cref="DiscordGuild"/>.</returns>
public bool Equals(DiscordGuild e)
=> e is not null && (ReferenceEquals(this, e) || this.Id == e.Id);
/// <summary>
/// Gets the hash code for this <see cref="DiscordGuild"/>.
/// </summary>
/// <returns>The hash code for this <see cref="DiscordGuild"/>.</returns>
public override int GetHashCode()
=> this.Id.GetHashCode();
/// <summary>
/// Gets whether the two <see cref="DiscordGuild"/> objects are equal.
/// </summary>
/// <param name="e1">First guild to compare.</param>
/// <param name="e2">Second guild to compare.</param>
/// <returns>Whether the two guilds are equal.</returns>
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);
}
/// <summary>
/// Gets whether the two <see cref="DiscordGuild"/> objects are not equal.
/// </summary>
/// <param name="e1">First guild to compare.</param>
/// <param name="e2">Second guild to compare.</param>
/// <returns>Whether the two guilds are not equal.</returns>
public static bool operator !=(DiscordGuild e1, DiscordGuild e2)
=> !(e1 == e2);
}
diff --git a/DisCatSharp/Entities/Guild/DiscordGuildEmoji.cs b/DisCatSharp/Entities/Guild/DiscordGuildEmoji.cs
index f7971f739..d041dd455 100644
--- a/DisCatSharp/Entities/Guild/DiscordGuildEmoji.cs
+++ b/DisCatSharp/Entities/Guild/DiscordGuildEmoji.cs
@@ -1,99 +1,99 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a guild emoji.
/// </summary>
public sealed class DiscordGuildEmoji : DiscordEmoji
{
/// <summary>
/// Gets the user that created this emoji.
/// </summary>
[JsonIgnore]
public DiscordUser User { get; internal set; }
/// <summary>
/// Gets the guild to which this emoji belongs.
/// </summary>
[JsonIgnore]
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordGuildEmoji"/> class.
/// </summary>
internal DiscordGuildEmoji()
{ }
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Modifies this emoji.
/// </summary>
/// <param name="name">New name for this emoji.</param>
/// <param name="roles">Roles for which this emoji will be available. This works only if your application is whitelisted as integration.</param>
/// <param name="reason">Reason for audit log.</param>
/// <returns>The modified emoji.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageEmojisAndStickers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the emoji does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordGuildEmoji> ModifyAsync(string name, IEnumerable<DiscordRole> roles = null, string reason = null)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> this.Guild.ModifyEmojiAsync(this, name, roles, reason);
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Deletes this emoji.
/// </summary>
/// <param name="reason">Reason for audit log.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageEmojisAndStickers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the emoji does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task DeleteAsync(string reason = null)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> this.Guild.DeleteEmojiAsync(this, reason);
}
diff --git a/DisCatSharp/Entities/Guild/DiscordGuildMembershipScreening.cs b/DisCatSharp/Entities/Guild/DiscordGuildMembershipScreening.cs
index 7ac7f7ca4..1c8132f79 100644
--- a/DisCatSharp/Entities/Guild/DiscordGuildMembershipScreening.cs
+++ b/DisCatSharp/Entities/Guild/DiscordGuildMembershipScreening.cs
@@ -1,52 +1,52 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a guild's membership screening form.
/// </summary>
public class DiscordGuildMembershipScreening
{
/// <summary>
/// Gets when the fields were last updated.
/// </summary>
[JsonProperty("version")]
public DateTimeOffset Version { get; internal set; }
/// <summary>
/// Gets the steps in the screening form.
/// </summary>
[JsonProperty("form_fields")]
public IReadOnlyList<DiscordGuildMembershipScreeningField> Fields { get; internal set; }
/// <summary>
/// Gets the server description shown in the screening form.
/// </summary>
[JsonProperty("description")]
public string Description { get; internal set; }
}
diff --git a/DisCatSharp/Entities/Guild/DiscordGuildMembershipScreeningField.cs b/DisCatSharp/Entities/Guild/DiscordGuildMembershipScreeningField.cs
index 6c8b52daf..d137d7f14 100644
--- a/DisCatSharp/Entities/Guild/DiscordGuildMembershipScreeningField.cs
+++ b/DisCatSharp/Entities/Guild/DiscordGuildMembershipScreeningField.cs
@@ -1,80 +1,80 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a field in a guild's membership screening form
/// </summary>
public class DiscordGuildMembershipScreeningField
{
/// <summary>
/// Gets the type of the field.
/// </summary>
[JsonProperty("field_type", NullValueHandling = NullValueHandling.Ignore)]
public MembershipScreeningFieldType Type { get; internal set; }
/// <summary>
/// Gets the title of the field.
/// </summary>
[JsonProperty("label", NullValueHandling = NullValueHandling.Ignore)]
public string Label { get; internal set; }
/// <summary>
/// Gets the list of rules
/// </summary>
[JsonProperty("values", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<string> Values { get; internal set; }
/// <summary>
/// Gets whether the user has to fill out this field
/// </summary>
[JsonProperty("required", NullValueHandling = NullValueHandling.Ignore)]
public bool IsRequired { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordGuildMembershipScreeningField"/> class.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="label">The label.</param>
/// <param name="values">The values.</param>
/// <param name="required">If true, required.</param>
public DiscordGuildMembershipScreeningField(MembershipScreeningFieldType type, string label, IEnumerable<string> values, bool required = true)
{
this.Type = type;
this.Label = label;
this.Values = values.ToList();
this.IsRequired = required;
}
/// <summary>
/// Initializes a new instance of the <see cref="DiscordGuildMembershipScreeningField"/> class.
/// </summary>
internal DiscordGuildMembershipScreeningField() { }
}
diff --git a/DisCatSharp/Entities/Guild/DiscordGuildPreview.cs b/DisCatSharp/Entities/Guild/DiscordGuildPreview.cs
index cc9289b1e..3ae318929 100644
--- a/DisCatSharp/Entities/Guild/DiscordGuildPreview.cs
+++ b/DisCatSharp/Entities/Guild/DiscordGuildPreview.cs
@@ -1,147 +1,147 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents the guild preview.
/// </summary>
public class DiscordGuildPreview : SnowflakeObject
{
/// <summary>
/// Gets the guild name.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; internal set; }
/// <summary>
/// Gets the guild icon's hash.
/// </summary>
[JsonProperty("icon", NullValueHandling = NullValueHandling.Ignore)]
public string IconHash { get; internal set; }
/// <summary>
/// Gets the guild icon's url.
/// </summary>
[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;
/// <summary>
/// Gets the guild splash's hash.
/// </summary>
[JsonProperty("splash", NullValueHandling = NullValueHandling.Ignore)]
public string SplashHash { get; internal set; }
/// <summary>
/// Gets the guild splash's url.
/// </summary>
[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;
/// <summary>
/// Gets the guild discovery splash's hash.
/// </summary>
[JsonProperty("discovery_splash", NullValueHandling = NullValueHandling.Ignore)]
public string DiscoverySplashHash { get; internal set; }
/// <summary>
/// Gets the guild discovery splash's url.
/// </summary>
[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;
/// <summary>
/// Gets a collection of this guild's emojis.
/// </summary>
[JsonIgnore]
public IReadOnlyDictionary<ulong, DiscordEmoji> Emojis => new ReadOnlyConcurrentDictionary<ulong, DiscordEmoji>(this.EmojisInternal);
[JsonProperty("emojis", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary<ulong, DiscordEmoji> EmojisInternal;
/// <summary>
/// Gets a collection of this guild's stickers.
/// </summary>
[JsonIgnore]
public IReadOnlyDictionary<ulong, DiscordSticker> Stickers => new ReadOnlyConcurrentDictionary<ulong, DiscordSticker>(this.StickersInternal);
[JsonProperty("stickers", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary<ulong, DiscordSticker> StickersInternal;
/// <summary>
/// Gets a collection of this guild's features.
/// </summary>
[JsonProperty("features", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<string> Features { get; internal set; }
/// <summary>
/// Gets the approximate member count.
/// </summary>
[JsonProperty("approximate_member_count")]
public int ApproximateMemberCount { get; internal set; }
/// <summary>
/// Gets the approximate presence count.
/// </summary>
[JsonProperty("approximate_presence_count")]
public int ApproximatePresenceCount { get; internal set; }
/// <summary>
/// Gets the description for the guild, if the guild is discoverable.
/// </summary>
[JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)]
public string Description { get; internal set; }
/// <summary>
/// Gets the system channel flags for the guild.
/// </summary>
[JsonProperty("system_channel_flags", NullValueHandling = NullValueHandling.Ignore)]
public SystemChannelFlags SystemChannelFlags { get; internal set; }
/// <summary>
/// Gets this hub type for the guild, if the guild is a hub.
/// </summary>
[JsonProperty("hub_type", NullValueHandling = NullValueHandling.Ignore)]
public HubType HubType { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordGuildPreview"/> class.
/// </summary>
internal DiscordGuildPreview()
{ }
}
diff --git a/DisCatSharp/Entities/Guild/DiscordGuildTemplate.cs b/DisCatSharp/Entities/Guild/DiscordGuildTemplate.cs
index f8e764360..14cfb9f66 100644
--- a/DisCatSharp/Entities/Guild/DiscordGuildTemplate.cs
+++ b/DisCatSharp/Entities/Guild/DiscordGuildTemplate.cs
@@ -1,107 +1,107 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Entities;
/// <summary>
/// Represents a guild template.
/// </summary>
public class DiscordGuildTemplate
{
/// <summary>
/// Gets the template code.
/// </summary>
[JsonProperty("code", NullValueHandling = NullValueHandling.Ignore)]
public string Code { get; internal set; }
/// <summary>
/// Gets the name of the template.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; internal set; }
/// <summary>
/// Gets the description of the template.
/// </summary>
[JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)]
public string Description { get; internal set; }
/// <summary>
/// Gets the number of times the template has been used.
/// </summary>
[JsonProperty("usage_count", NullValueHandling = NullValueHandling.Ignore)]
public int UsageCount { get; internal set; }
/// <summary>
/// Gets the ID of the creator of the template.
/// </summary>
[JsonProperty("creator_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong CreatorId { get; internal set; }
/// <summary>
/// Gets the creator of the template.
/// </summary>
[JsonProperty("creator", NullValueHandling = NullValueHandling.Ignore)]
public DiscordUser Creator { get; internal set; }
/// <summary>
/// Date the template was created.
/// </summary>
[JsonProperty("created_at", NullValueHandling = NullValueHandling.Ignore)]
public DateTimeOffset CreatedAt { get; internal set; }
/// <summary>
/// Date the template was updated.
/// </summary>
[JsonProperty("updated_at", NullValueHandling = NullValueHandling.Ignore)]
public DateTimeOffset UpdatedAt { get; internal set; }
/// <summary>
/// Gets the ID of the source guild.
/// </summary>
[JsonProperty("source_guild_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong SourceGuildId { get; internal set; }
/// <summary>
/// Gets the source guild.
/// </summary>
[JsonProperty("serialized_source_guild", NullValueHandling = NullValueHandling.Ignore)]
public DiscordGuild SourceGuild { get; internal set; }
/// <summary>
/// Gets whether the template has unsynced changes.
/// </summary>
[JsonProperty("is_dirty", NullValueHandling = NullValueHandling.Ignore)]
public bool? IsDirty { get; internal set; }
/// <summary>
/// Gets whether the template has unsynced changes.
/// </summary>
/// <remarks><see cref="IsDirty"/></remarks>
[JsonIgnore]
public bool? IsUnsynced
=> this.IsDirty;
}
diff --git a/DisCatSharp/Entities/Guild/DiscordGuildWelcomeScreen.cs b/DisCatSharp/Entities/Guild/DiscordGuildWelcomeScreen.cs
index 9d5ebe97c..35f1fbcf7 100644
--- a/DisCatSharp/Entities/Guild/DiscordGuildWelcomeScreen.cs
+++ b/DisCatSharp/Entities/Guild/DiscordGuildWelcomeScreen.cs
@@ -1,45 +1,45 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a discord welcome screen object.
/// </summary>
public class DiscordGuildWelcomeScreen
{
/// <summary>
/// Gets the server description shown in the welcome screen.
/// </summary>
[JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)]
public string Description { get; internal set; }
/// <summary>
/// Gets the channels shown in the welcome screen.
/// </summary>
[JsonProperty("welcome_channels", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<DiscordGuildWelcomeScreenChannel> WelcomeChannels { get; internal set; }
}
diff --git a/DisCatSharp/Entities/Guild/DiscordGuildWelcomeScreenChannel.cs b/DisCatSharp/Entities/Guild/DiscordGuildWelcomeScreenChannel.cs
index 997241bd1..0d75542b8 100644
--- a/DisCatSharp/Entities/Guild/DiscordGuildWelcomeScreenChannel.cs
+++ b/DisCatSharp/Entities/Guild/DiscordGuildWelcomeScreenChannel.cs
@@ -1,74 +1,74 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a channel in a welcome screen
/// </summary>
public class DiscordGuildWelcomeScreenChannel
{
/// <summary>
/// Gets the id of the channel.
/// </summary>
[JsonProperty("channel_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong ChannelId { get; internal set; }
/// <summary>
/// Gets the description shown for the channel.
/// </summary>
[JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)]
public string Description { get; internal set; }
/// <summary>
/// Gets the emoji id if the emoji is custom, when applicable.
/// </summary>
[JsonProperty("emoji_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? EmojiId { get; internal set; }
/// <summary>
/// Gets the name of the emoji if custom or the unicode character if standard, when applicable.
/// </summary>
[JsonProperty("emoji_name", NullValueHandling = NullValueHandling.Ignore)]
public string EmojiName { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordGuildWelcomeScreenChannel"/> class.
/// </summary>
/// <param name="channelId">The channel id.</param>
/// <param name="description">The description.</param>
/// <param name="emoji">The emoji.</param>
public DiscordGuildWelcomeScreenChannel(ulong channelId, string description, DiscordEmoji emoji = null)
{
this.ChannelId = channelId;
this.Description = description;
if (emoji != null)
{
if (emoji.Id == 0)
this.EmojiName = emoji.Name;
else
this.EmojiId = emoji.Id;
}
}
}
diff --git a/DisCatSharp/Entities/Guild/DiscordMember.cs b/DisCatSharp/Entities/Guild/DiscordMember.cs
index 3a5096f26..79b4194ab 100644
--- a/DisCatSharp/Entities/Guild/DiscordMember.cs
+++ b/DisCatSharp/Entities/Guild/DiscordMember.cs
@@ -1,722 +1,722 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Exceptions;
using DisCatSharp.Net;
using DisCatSharp.Net.Abstractions;
using DisCatSharp.Net.Models;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a Discord guild member.
/// </summary>
public class DiscordMember : DiscordUser, IEquatable<DiscordMember>
{
/// <summary>
/// Initializes a new instance of the <see cref="DiscordMember"/> class.
/// </summary>
internal DiscordMember()
{
this._roleIdsLazy = new Lazy<IReadOnlyList<ulong>>(() => new ReadOnlyCollection<ulong>(this.RoleIdsInternal));
}
/// <summary>
/// Initializes a new instance of the <see cref="DiscordMember"/> class.
/// </summary>
/// <param name="user">The user.</param>
internal DiscordMember(DiscordUser user)
{
this.Discord = user.Discord;
this.Id = user.Id;
this.RoleIdsInternal = new List<ulong>();
this._roleIdsLazy = new Lazy<IReadOnlyList<ulong>>(() => new ReadOnlyCollection<ulong>(this.RoleIdsInternal));
}
/// <summary>
/// Initializes a new instance of the <see cref="DiscordMember"/> class.
/// </summary>
/// <param name="mbr">The mbr.</param>
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.GuildPronouns = mbr.GuildPronouns;
this.CommunicationDisabledUntil = mbr.CommunicationDisabledUntil;
this.AvatarHashInternal = mbr.AvatarHash;
this.RoleIdsInternal = mbr.Roles ?? new List<ulong>();
this._roleIdsLazy = new Lazy<IReadOnlyList<ulong>>(() => new ReadOnlyCollection<ulong>(this.RoleIdsInternal));
this.MemberFlags = mbr.MemberFlags;
}
/// <summary>
/// Gets the members avatar hash.
/// </summary>
[JsonProperty("avatar", NullValueHandling = NullValueHandling.Ignore)]
public virtual string GuildAvatarHash { get; internal set; }
/// <summary>
/// Gets the members avatar URL.
/// </summary>
[JsonIgnore]
public string GuildAvatarUrl
=> string.IsNullOrWhiteSpace(this.GuildAvatarHash) ? this.User.AvatarUrl : $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.GUILDS}/{this.GuildId.ToString(CultureInfo.InvariantCulture)}{Endpoints.USERS}/{this.Id.ToString(CultureInfo.InvariantCulture)}{Endpoints.AVATARS}/{this.GuildAvatarHash}.{(this.GuildAvatarHash.StartsWith("a_") ? "gif" : "png")}?size=1024";
/// <summary>
/// Gets the members banner hash.
/// </summary>
[JsonProperty("banner", NullValueHandling = NullValueHandling.Ignore)]
public virtual string GuildBannerHash { get; internal set; }
/// <summary>
/// Gets the members banner URL.
/// </summary>
[JsonIgnore]
public string GuildBannerUrl
=> string.IsNullOrWhiteSpace(this.GuildBannerHash) ? this.User.BannerUrl : $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.GUILDS}/{this.GuildId.ToString(CultureInfo.InvariantCulture)}{Endpoints.USERS}/{this.Id.ToString(CultureInfo.InvariantCulture)}{Endpoints.BANNERS}/{this.GuildBannerHash}.{(this.GuildBannerHash.StartsWith("a_") ? "gif" : "png")}?size=1024";
/// <summary>
/// The color of this member's banner. Mutually exclusive with <see cref="GuildBannerHash"/>.
/// </summary>
[JsonIgnore]
public override DiscordColor? BannerColor
=> this.User.BannerColor;
/// <summary>
/// Gets this member's nickname.
/// </summary>
[JsonProperty("nick", NullValueHandling = NullValueHandling.Ignore)]
public string Nickname { get; internal set; }
/// <summary>
/// Gets the members guild bio.
/// This is not available to bots tho.
/// </summary>
[JsonProperty("bio", NullValueHandling = NullValueHandling.Ignore)]
public string GuildBio { get; internal set; }
/// <summary>
/// Gets the members's pronouns.
/// </summary>
[JsonProperty("pronouns", NullValueHandling = NullValueHandling.Ignore)]
public string GuildPronouns { get; internal set; }
/// <summary>
/// Gets the members flags.
/// </summary>
[JsonProperty("flags", NullValueHandling = NullValueHandling.Ignore)]
public MemberFlags MemberFlags { get; internal set; }
[JsonIgnore]
internal string AvatarHashInternal;
/// <summary>
/// Gets this member's display name.
/// </summary>
[JsonIgnore]
public string DisplayName
=> this.Nickname ?? this.Username;
/// <summary>
/// List of role ids
/// </summary>
[JsonIgnore]
internal IReadOnlyList<ulong> RoleIds
=> this._roleIdsLazy.Value;
[JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)]
internal List<ulong> RoleIdsInternal;
[JsonIgnore]
private readonly Lazy<IReadOnlyList<ulong>> _roleIdsLazy;
/// <summary>
/// Gets the list of roles associated with this member.
/// </summary>
[JsonIgnore]
public IReadOnlyList<DiscordRole> Roles
=> this.RoleIds.Select(id => this.Guild.GetRole(id)).Where(x => x != null).ToList();
/// <summary>
/// Gets the color associated with this user's top color-giving role, otherwise 0 (no color).
/// </summary>
[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();
}
}
/// <summary>
/// Date the user joined the guild
/// </summary>
[JsonProperty("joined_at", NullValueHandling = NullValueHandling.Ignore)]
public DateTimeOffset JoinedAt { get; internal set; }
/// <summary>
/// Date the user started boosting this server
/// </summary>
[JsonProperty("premium_since", NullValueHandling = NullValueHandling.Ignore)]
public DateTimeOffset? PremiumSince { get; internal set; }
/// <summary>
/// Date until the can communicate again.
/// </summary>
[JsonProperty("communication_disabled_until", NullValueHandling = NullValueHandling.Include)]
public DateTime? CommunicationDisabledUntil { get; internal set; }
/// <summary>
/// If the user is deafened
/// </summary>
[JsonProperty("is_deafened", NullValueHandling = NullValueHandling.Ignore)]
public bool IsDeafened { get; internal set; }
/// <summary>
/// If the user is muted
/// </summary>
[JsonProperty("is_muted", NullValueHandling = NullValueHandling.Ignore)]
public bool IsMuted { get; internal set; }
/// <summary>
/// Whether the user has not passed the guild's Membership Screening requirements yet.
/// </summary>
[JsonProperty("pending", NullValueHandling = NullValueHandling.Ignore)]
public bool? IsPending { get; internal set; }
/// <summary>
/// Gets this member's voice state.
/// </summary>
[JsonIgnore]
public DiscordVoiceState VoiceState
=> this.Discord.Guilds[this.GuildId].VoiceStates.TryGetValue(this.Id, out var voiceState) ? voiceState : null;
[JsonIgnore]
internal ulong GuildId = 0;
/// <summary>
/// Gets the guild of which this member is a part of.
/// </summary>
[JsonIgnore]
public DiscordGuild Guild
=> this.Discord.Guilds[this.GuildId];
/// <summary>
/// Gets whether this member is the Guild owner.
/// </summary>
[JsonIgnore]
public bool IsOwner
=> this.Id == this.Guild.OwnerId;
/// <summary>
/// Gets the member's position in the role hierarchy, which is the member's highest role's position. Returns <see cref="int.MaxValue"/> for the guild's owner.
/// </summary>
[JsonIgnore]
public int Hierarchy
=> this.IsOwner ? int.MaxValue : this.RoleIds.Count == 0 ? 0 : this.Roles.Max(x => x.Position);
/// <summary>
/// Gets the permissions for the current member.
/// </summary>
[JsonIgnore]
public Permissions Permissions
=> this.GetPermissions();
#region Overridden user properties
/// <summary>
/// Gets the user.
/// </summary>
[JsonIgnore]
internal DiscordUser User
=> this.Discord.UserCache[this.Id];
/// <summary>
/// Gets this member's username.
/// </summary>
[JsonIgnore]
public override string Username
{
get => this.User.Username;
internal set => this.User.Username = value;
}
/// <summary>
/// Gets the member's 4-digit discriminator.
/// </summary>
[JsonIgnore]
public override string Discriminator
{
get => this.User.Discriminator;
internal set => this.User.Discriminator = value;
}
/// <summary>
/// Gets the member's avatar hash.
/// </summary>
[JsonIgnore]
public override string AvatarHash
{
get => this.User.AvatarHash;
internal set => this.User.AvatarHash = value;
}
/// <summary>
/// Gets the member's banner hash.
/// </summary>
[JsonIgnore]
public override string BannerHash
{
get => this.User.BannerHash;
internal set => this.User.BannerHash = value;
}
/// <summary>
/// Gets whether the member is a bot.
/// </summary>
[JsonIgnore]
public override bool IsBot
{
get => this.User.IsBot;
internal set => this.User.IsBot = value;
}
/// <summary>
/// Gets the member's email address.
/// <para>This is only present in OAuth.</para>
/// </summary>
[JsonIgnore]
public override string Email
{
get => this.User.Email;
internal set => this.User.Email = value;
}
/// <summary>
/// Gets whether the member has multi-factor authentication enabled.
/// </summary>
[JsonIgnore]
public override bool? MfaEnabled
{
get => this.User.MfaEnabled;
internal set => this.User.MfaEnabled = value;
}
/// <summary>
/// Gets whether the member is verified.
/// <para>This is only present in OAuth.</para>
/// </summary>
[JsonIgnore]
public override bool? Verified
{
get => this.User.Verified;
internal set => this.User.Verified = value;
}
/// <summary>
/// Gets the member's chosen language
/// </summary>
[JsonIgnore]
public override string Locale
{
get => this.User.Locale;
internal set => this.User.Locale = value;
}
/// <summary>
/// Gets the user's flags.
/// </summary>
[JsonIgnore]
public override UserFlags? OAuthFlags
{
get => this.User.OAuthFlags;
internal set => this.User.OAuthFlags = value;
}
/// <summary>
/// Gets the member's flags for OAuth.
/// </summary>
[JsonIgnore]
public override UserFlags? Flags
{
get => this.User.Flags;
internal set => this.User.Flags = value;
}
#endregion
/// <summary>
/// Sets this member's voice mute status.
/// </summary>
/// <param name="mute">Whether the member is to be muted.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.MuteMembers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task SetMuteAsync(bool mute, string reason = null)
=> this.Discord.ApiClient.ModifyGuildMemberAsync(this.GuildId, this.Id, default, default, mute, default, default, default, this.MemberFlags, reason);
public Task VerifyAsync(string reason = null)
=> this.Discord.ApiClient.ModifyGuildMemberAsync(this.GuildId, this.Id, default, default, default, default, default, true, this.MemberFlags, reason);
public Task UnverifyAsync(string reason = null)
=> this.Discord.ApiClient.ModifyGuildMemberAsync(this.GuildId, this.Id, default, default, default, default, default, false, this.MemberFlags, reason);
/// <summary>
/// Sets this member's voice deaf status.
/// </summary>
/// <param name="deaf">Whether the member is to be deafened.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.DeafenMembers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task SetDeafAsync(bool deaf, string reason = null)
=> this.Discord.ApiClient.ModifyGuildMemberAsync(this.GuildId, this.Id, default, default, default, deaf, default, default, this.MemberFlags, reason);
/// <summary>
/// Modifies this member.
/// </summary>
/// <param name="action">Action to perform on this member.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageNicknames"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task ModifyAsync(Action<MemberEditModel> 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(action));
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.None,
mdl.Roles.Map(e => e.Select(xr => xr.Id)), mdl.Muted, mdl.Deafened,
mdl.VoiceChannel.Map(e => e?.Id), default, this.MemberFlags, mdl.AuditLogReason).ConfigureAwait(false);
}
else
{
await this.Discord.ApiClient.ModifyGuildMemberAsync(this.Guild.Id, this.Id, mdl.Nickname,
mdl.Roles.Map(e => e.Select(xr => xr.Id)), mdl.Muted, mdl.Deafened,
mdl.VoiceChannel.Map(e => e?.Id), default, this.MemberFlags, mdl.AuditLogReason).ConfigureAwait(false);
}
}
/// <summary>
/// Disconnects the member from their current voice channel.
/// </summary>
public async Task DisconnectFromVoiceAsync()
=> await this.ModifyAsync(x => x.VoiceChannel = null);
/// <summary>
/// Adds a timeout to a member.
/// </summary>
/// <param name="until">The datetime offset to time out the user. Up to 28 days.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ModerateMembers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
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);
/// <summary>
/// Adds a timeout to a member.
/// </summary>
/// <param name="until">The timespan to time out the user. Up to 28 days.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ModerateMembers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task TimeoutAsync(TimeSpan until, string reason = null)
=> this.TimeoutAsync(DateTimeOffset.UtcNow + until, reason);
/// <summary>
/// Adds a timeout to a member.
/// </summary>
/// <param name="until">The datetime to time out the user. Up to 28 days.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ModerateMembers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task TimeoutAsync(DateTime until, string reason = null)
=> this.TimeoutAsync(until.ToUniversalTime() - DateTime.UtcNow, reason);
/// <summary>
/// Removes the timeout from a member.
/// </summary>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ModerateMembers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task RemoveTimeoutAsync(string reason = null)
=> this.Discord.ApiClient.ModifyTimeoutAsync(this.Guild.Id, this.Id, null, reason);
/// <summary>
/// Grants a role to the member.
/// </summary>
/// <param name="role">Role to grant.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageRoles"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task GrantRoleAsync(DiscordRole role, string reason = null)
=> this.Discord.ApiClient.AddGuildMemberRoleAsync(this.Guild.Id, this.Id, role.Id, reason);
/// <summary>
/// Revokes a role from a member.
/// </summary>
/// <param name="role">Role to revoke.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageRoles"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task RevokeRoleAsync(DiscordRole role, string reason = null)
=> this.Discord.ApiClient.RemoveGuildMemberRoleAsync(this.Guild.Id, this.Id, role.Id, reason);
/// <summary>
/// Sets the member's roles to ones specified.
/// </summary>
/// <param name="roles">Roles to set.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageRoles"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task ReplaceRolesAsync(IEnumerable<DiscordRole> roles, string reason = null)
=> this.Discord.ApiClient.ModifyGuildMemberAsync(this.Guild.Id, this.Id, default,
Optional.Some(roles.Select(xr => xr.Id)), default, default, default, default, this.MemberFlags, reason);
/// <summary>
/// Bans this member from their guild.
/// </summary>
/// <param name="deleteMessageDays">How many days to remove messages from.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.BanMembers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task BanAsync(int deleteMessageDays = 0, string reason = null)
=> this.Guild.BanMemberAsync(this, deleteMessageDays, reason);
/// <summary>
/// Unbans this member from their guild.
/// </summary>
/// <exception cref = "Exceptions.UnauthorizedException" > Thrown when the client does not have the<see cref="Permissions.BanMembers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task UnbanAsync(string reason = null)
=> this.Guild.UnbanMemberAsync(this, reason);
/// <summary>
/// Kicks this member from their guild.
/// </summary>
/// <param name="reason">Reason for audit logs.</param>
/// <returns></returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.KickMembers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task RemoveAsync(string reason = null)
=> this.Discord.ApiClient.RemoveGuildMemberAsync(this.GuildId, this.Id, reason);
/// <summary>
/// Moves this member to the specified voice channel
/// </summary>
/// <param name="channel"></param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.MoveMembers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task PlaceInAsync(DiscordChannel channel)
=> channel.PlaceMemberAsync(this);
/// <summary>
/// Updates the member's suppress state in a stage channel.
/// </summary>
/// <param name="channel">The channel the member is currently in.</param>
/// <param name="suppress">Toggles the member's suppress state.</param>
/// <exception cref="ArgumentException">Thrown when the channel in not a voice channel.</exception>
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);
}
/// <summary>
/// Makes the user a speaker.
/// </summary>
/// <exception cref="ArgumentException">Thrown when the user is not inside an stage channel.</exception>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.MuteMembers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
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);
}
/// <summary>
/// Moves the user to audience.
/// </summary>
/// <exception cref="ArgumentException">Thrown when the user is not inside an stage channel.</exception>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.MuteMembers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
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);
}
/// <summary>
/// Calculates permissions in a given channel for this member.
/// </summary>
/// <param name="channel">Channel to calculate permissions for.</param>
/// <returns>Calculated permissions for this member in the channel.</returns>
public Permissions PermissionsIn(DiscordChannel channel)
=> channel.PermissionsFor(this);
/// <summary>
/// Get's the current member's roles based on the sum of the permissions of their given roles.
/// </summary>
private Permissions GetPermissions()
{
if (this.Guild.OwnerId == this.Id)
return PermissionMethods.FullPerms;
Permissions perms;
// assign @everyone permissions
var everyoneRole = this.Guild.EveryoneRole;
perms = everyoneRole.Permissions;
// assign permissions from member's roles (in order)
perms |= this.Roles.Aggregate(Permissions.None, (c, role) => c | role.Permissions);
// Administrator grants all permissions and cannot be overridden
return (perms & Permissions.Administrator) == Permissions.Administrator ? PermissionMethods.FullPerms : perms;
}
/// <summary>
/// Returns a string representation of this member.
/// </summary>
/// <returns>String representation of this member.</returns>
public override string ToString()
=> $"Member {this.Id}; {this.Username}#{this.Discriminator} ({this.DisplayName})";
/// <summary>
/// Checks whether this <see cref="DiscordMember"/> is equal to another object.
/// </summary>
/// <param name="obj">Object to compare to.</param>
/// <returns>Whether the object is equal to this <see cref="DiscordMember"/>.</returns>
public override bool Equals(object obj)
=> this.Equals(obj as DiscordMember);
/// <summary>
/// Checks whether this <see cref="DiscordMember"/> is equal to another <see cref="DiscordMember"/>.
/// </summary>
/// <param name="e"><see cref="DiscordMember"/> to compare to.</param>
/// <returns>Whether the <see cref="DiscordMember"/> is equal to this <see cref="DiscordMember"/>.</returns>
public bool Equals(DiscordMember e)
=> e is not null && (ReferenceEquals(this, e) || (this.Id == e.Id && this.GuildId == e.GuildId));
/// <summary>
/// Gets the hash code for this <see cref="DiscordMember"/>.
/// </summary>
/// <returns>The hash code for this <see cref="DiscordMember"/>.</returns>
public override int GetHashCode()
{
var hash = 13;
hash = (hash * 7) + this.Id.GetHashCode();
hash = (hash * 7) + this.GuildId.GetHashCode();
return hash;
}
/// <summary>
/// Gets whether the two <see cref="DiscordMember"/> objects are equal.
/// </summary>
/// <param name="e1">First member to compare.</param>
/// <param name="e2">Second member to compare.</param>
/// <returns>Whether the two members are equal.</returns>
public static bool operator ==(DiscordMember e1, DiscordMember e2)
{
var o1 = e1 as object;
var o2 = e2 as object;
return (o1 != null || o2 == null) && (o1 == null || o2 != null) && ((o1 == null && o2 == null) || (e1.Id == e2.Id && e1.GuildId == e2.GuildId));
}
/// <summary>
/// Gets whether the two <see cref="DiscordMember"/> objects are not equal.
/// </summary>
/// <param name="e1">First member to compare.</param>
/// <param name="e2">Second member to compare.</param>
/// <returns>Whether the two members are not equal.</returns>
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 bcf951fa0..3b130f4c1 100644
--- a/DisCatSharp/Entities/Guild/DiscordRole.cs
+++ b/DisCatSharp/Entities/Guild/DiscordRole.cs
@@ -1,333 +1,333 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a discord role, to which users can be assigned.
/// </summary>
public class DiscordRole : SnowflakeObject, IEquatable<DiscordRole>
{
/// <summary>
/// Gets the name of this role.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; internal set; }
/// <summary>
/// Gets the description of this role.
/// </summary>
[JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)]
public string Description { get; internal set; }
/// <summary>
/// Gets the color of this role.
/// </summary>
[JsonIgnore]
public DiscordColor Color
=> new(this.ColorInternal);
[JsonProperty("color", NullValueHandling = NullValueHandling.Ignore)]
internal int ColorInternal;
/// <summary>
/// Gets whether this role is hoisted.
/// </summary>
[JsonProperty("hoist", NullValueHandling = NullValueHandling.Ignore)]
public bool IsHoisted { get; internal set; }
/// <summary>
/// Gets the position of this role in the role hierarchy.
/// </summary>
[JsonProperty("position", NullValueHandling = NullValueHandling.Ignore)]
public int Position { get; internal set; }
/// <summary>
/// Gets the permissions set for this role.
/// </summary>
[JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)]
public Permissions Permissions { get; internal set; }
/// <summary>
/// Gets whether this role is managed by an integration.
/// </summary>
[JsonProperty("managed", NullValueHandling = NullValueHandling.Ignore)]
public bool IsManaged { get; internal set; }
/// <summary>
/// Gets whether this role is mentionable.
/// </summary>
[JsonProperty("mentionable", NullValueHandling = NullValueHandling.Ignore)]
public bool IsMentionable { get; internal set; }
/// <summary>
/// Gets the tags this role has.
/// </summary>
[JsonProperty("tags", NullValueHandling = NullValueHandling.Ignore)]
public DiscordRoleTags Tags { get; internal set; }
/// <summary>
/// Gets the role icon's hash.
/// </summary>
[JsonProperty("icon", NullValueHandling = NullValueHandling.Ignore)]
public string IconHash { get; internal set; }
/// <summary>
/// Gets the role icon's url.
/// </summary>
[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;
/// <summary>
/// Gets the role unicode_emoji.
/// </summary>
[JsonProperty("unicode_emoji", NullValueHandling = NullValueHandling.Ignore)]
internal string UnicodeEmojiString;
/// <summary>
/// Gets the unicode emoji.
/// </summary>
public DiscordEmoji UnicodeEmoji
=> this.UnicodeEmojiString != null ? DiscordEmoji.FromName(this.Discord, $":{this.UnicodeEmojiString}:", false) : null;
[JsonIgnore]
internal ulong GuildId = 0;
/// <summary>
/// Gets the role flags.
/// </summary>
[JsonProperty("flags", NullValueHandling = NullValueHandling.Ignore)]
public RoleFlags Flags { get; internal set; }
/// <summary>
/// Gets a mention string for this role. If the role is mentionable, this string will mention all the users that belong to this role.
/// </summary>
public string Mention
=> Formatter.Mention(this);
#region Methods
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Modifies this role's position.
/// </summary>
/// <param name="position">New position</param>
/// <param name="reason">Reason why we moved it</param>
/// <returns></returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageRoles"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the role does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task ModifyPositionAsync(int position, string reason = null)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
{
var roles = this.Discord.Guilds[this.GuildId].Roles.Values.OrderByDescending(xr => xr.Position)
.Select(x => new RestGuildRoleReorderPayload
{
RoleId = x.Id,
Position = x.Id == this.Id
? position
: x.Position <= position ? x.Position - 1 : x.Position
});
return this.Discord.ApiClient.ModifyGuildRolePositionAsync(this.GuildId, roles, reason);
}
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Updates this role.
/// </summary>
/// <param name="name">New role name.</param>
/// <param name="permissions">New role permissions.</param>
/// <param name="color">New role color.</param>
/// <param name="hoist">New role hoist.</param>
/// <param name="mentionable">Whether this role is mentionable.</param>
/// <param name="reason">Audit log reason.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageRoles"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the role does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task ModifyAsync(string name = null, Permissions? permissions = null, DiscordColor? color = null, bool? hoist = null, bool? mentionable = null, string reason = null)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> this.Discord.ApiClient.ModifyGuildRoleAsync(this.GuildId, this.Id, name, permissions, color?.Value, hoist, mentionable, null, null, reason);
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Updates this role.
/// </summary>
/// <param name="action">The action.</param>
/// <exception cref = "Exceptions.UnauthorizedException" > Thrown when the client does not have the<see cref="Permissions.ManageRoles"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the role does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task ModifyAsync(Action<RoleEditModel> action)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
{
var mdl = new RoleEditModel();
action(mdl);
var canContinue = true;
if ((mdl.Icon.HasValue && mdl.Icon.Value != null) || (mdl.UnicodeEmoji.HasValue && mdl.UnicodeEmoji.Value != null))
canContinue = this.Discord.Guilds[this.GuildId].Features.HasFeature(GuildFeaturesEnum.CanSetRoleIcons);
var iconb64 = Optional.FromNullable<string>(null);
if (mdl.Icon.HasValue && mdl.Icon.Value != null)
iconb64 = ImageTool.Base64FromStream(mdl.Icon);
else if (mdl.Icon.HasValue)
iconb64 = Optional.Some<string>(null);
var emoji = Optional.FromNullable<string>(null);
if (mdl.UnicodeEmoji.HasValue && mdl.UnicodeEmoji.Value != null)
emoji = mdl.UnicodeEmoji
.MapOrNull(e => e.Id == 0
? e.Name
: throw new ArgumentException("Emoji must be unicode"));
else if (mdl.UnicodeEmoji.HasValue)
emoji = Optional.Some<string>(null);
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.");
}
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Deletes this role.
/// </summary>
/// <param name="reason">Reason as to why this role has been deleted.</param>
/// <returns></returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageRoles"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the role does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task DeleteAsync(string reason = null)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> this.Discord.ApiClient.DeleteRoleAsync(this.GuildId, this.Id, reason);
#endregion
/// <summary>
/// Initializes a new instance of the <see cref="DiscordRole"/> class.
/// </summary>
internal DiscordRole()
{ }
/// <summary>
/// Checks whether this role has specific permissions.
/// </summary>
/// <param name="permission">Permissions to check for.</param>
/// <returns>Whether the permissions are allowed or not.</returns>
public PermissionLevel CheckPermission(Permissions permission)
=> (this.Permissions & permission) != 0 ? PermissionLevel.Allowed : PermissionLevel.Unset;
/// <summary>
/// Returns a string representation of this role.
/// </summary>
/// <returns>String representation of this role.</returns>
public override string ToString()
=> $"Role {this.Id}; {this.Name}";
/// <summary>
/// Checks whether this <see cref="DiscordRole"/> is equal to another object.
/// </summary>
/// <param name="obj">Object to compare to.</param>
/// <returns>Whether the object is equal to this <see cref="DiscordRole"/>.</returns>
public override bool Equals(object obj)
=> this.Equals(obj as DiscordRole);
/// <summary>
/// Checks whether this <see cref="DiscordRole"/> is equal to another <see cref="DiscordRole"/>.
/// </summary>
/// <param name="e"><see cref="DiscordRole"/> to compare to.</param>
/// <returns>Whether the <see cref="DiscordRole"/> is equal to this <see cref="DiscordRole"/>.</returns>
public bool Equals(DiscordRole e)
=> e switch
{
null => false,
_ => ReferenceEquals(this, e) || this.Id == e.Id
};
/// <summary>
/// Gets the hash code for this <see cref="DiscordRole"/>.
/// </summary>
/// <returns>The hash code for this <see cref="DiscordRole"/>.</returns>
public override int GetHashCode()
=> this.Id.GetHashCode();
/// <summary>
/// Gets whether the two <see cref="DiscordRole"/> objects are equal.
/// </summary>
/// <param name="e1">First role to compare.</param>
/// <param name="e2">Second role to compare.</param>
/// <returns>Whether the two roles are equal.</returns>
public static bool operator ==(DiscordRole e1, DiscordRole e2)
=> e1 is null == e2 is null
&& ((e1 is null && e2 is null) || e1.Id == e2.Id);
/// <summary>
/// Gets whether the two <see cref="DiscordRole"/> objects are not equal.
/// </summary>
/// <param name="e1">First role to compare.</param>
/// <param name="e2">Second role to compare.</param>
/// <returns>Whether the two roles are not equal.</returns>
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 ff253ccb3..56f5c151b 100644
--- a/DisCatSharp/Entities/Guild/DiscordRoleTags.cs
+++ b/DisCatSharp/Entities/Guild/DiscordRoleTags.cs
@@ -1,74 +1,74 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a discord role tags.
/// </summary>
public class DiscordRoleTags
{
/// <summary>
/// Gets the id of the bot this role belongs to.
/// </summary>
[JsonProperty("bot_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? BotId { get; internal set; }
/// <summary>
/// Gets the id of the integration this role belongs to.
/// </summary>
[JsonProperty("integration_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? IntegrationId { get; internal set; }
/// <summary>
/// Gets whether this is the guild's booster role.
/// </summary>
[JsonIgnore]
public bool IsPremiumSubscriber
=> this.PremiumSubscriber.HasValue && this.PremiumSubscriber.Value;
[JsonProperty("premium_subscriber", NullValueHandling = NullValueHandling.Include)]
internal bool? PremiumSubscriber = false;
/// <summary>
/// The id of this role's subscription sku and listing.
/// </summary>
[JsonProperty("subscription_listing_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? SubscriptionListingId { get; internal set; }
/// <summary>
/// Whether this role is available for purchase.
/// </summary>
[JsonProperty("available_for_purchase", NullValueHandling = NullValueHandling.Ignore)]
public bool? AvailableForPurchase { get; internal set; }
/// <summary>
/// Gets whether this is the guild's premium subscriber role.
/// </summary>
[JsonIgnore]
public bool IsLinkedRole
=> this.GuildConnection.HasValue && this.GuildConnection.Value;
[JsonProperty("guild_connections", NullValueHandling = NullValueHandling.Include)]
internal bool? GuildConnection = false;
}
diff --git a/DisCatSharp/Entities/Guild/ScheduledEvent/DiscordScheduledEvent.cs b/DisCatSharp/Entities/Guild/ScheduledEvent/DiscordScheduledEvent.cs
index 67d3d78c8..f4f1683b5 100644
--- a/DisCatSharp/Entities/Guild/ScheduledEvent/DiscordScheduledEvent.cs
+++ b/DisCatSharp/Entities/Guild/ScheduledEvent/DiscordScheduledEvent.cs
@@ -1,381 +1,381 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.Net.Models;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents an scheduled event.
/// </summary>
public class DiscordScheduledEvent : SnowflakeObject, IEquatable<DiscordScheduledEvent>
{
/// <summary>
/// Gets the guild id of the associated scheduled event.
/// </summary>
[JsonProperty("guild_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong GuildId { get; internal set; }
/// <summary>
/// Gets the guild to which this scheduled event belongs.
/// </summary>
[JsonIgnore]
public DiscordGuild Guild
=> this.Discord.Guilds.TryGetValue(this.GuildId, out var guild) ? guild : null;
/// <summary>
/// Gets the associated channel.
/// </summary>
[JsonIgnore]
public Task<DiscordChannel> Channel
=> this.ChannelId.HasValue ? this.Discord.ApiClient.GetChannelAsync(this.ChannelId.Value) : null;
/// <summary>
/// Gets id of the associated channel id.
/// </summary>
[JsonProperty("channel_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? ChannelId { get; internal set; }
/// <summary>
/// Gets the ID of the user that created the scheduled event.
/// </summary>
[JsonProperty("creator_id")]
public ulong CreatorId { get; internal set; }
/// <summary>
/// Gets the user that created the scheduled event.
/// </summary>
[JsonProperty("creator")]
public DiscordUser Creator { get; internal set; }
/// <summary>
/// Gets the member that created the scheduled event.
/// </summary>
[JsonIgnore]
public DiscordMember CreatorMember
=> this.Guild.MembersInternal.TryGetValue(this.CreatorId, out var owner)
? owner
: this.Discord.ApiClient.GetGuildMemberAsync(this.GuildId, this.CreatorId).Result;
/// <summary>
/// Gets the name of the scheduled event.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; internal set; }
/// <summary>
/// Gets the description of the scheduled event.
/// </summary>
[JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)]
public string Description { get; internal set; }
/// <summary>
/// Gets this event's cover hash, when applicable.
/// </summary>
[JsonProperty("image", NullValueHandling = NullValueHandling.Include)]
public string CoverImageHash { get; internal set; }
/// <summary>
/// Gets this event's cover in url form.
/// </summary>
[JsonIgnore]
public string CoverImageUrl
=> !string.IsNullOrWhiteSpace(this.CoverImageHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Uri}{Endpoints.GUILD_EVENTS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.CoverImageHash}.png" : null;
/// <summary>
/// Gets the scheduled start time of the scheduled event.
/// </summary>
[JsonIgnore]
public DateTimeOffset? ScheduledStartTime
=> !string.IsNullOrWhiteSpace(this.ScheduledStartTimeRaw) && DateTimeOffset.TryParse(this.ScheduledStartTimeRaw, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dto) ?
dto : null;
/// <summary>
/// Gets the scheduled start time of the scheduled event as raw string.
/// </summary>
[JsonProperty("scheduled_start_time", NullValueHandling = NullValueHandling.Ignore)]
internal string ScheduledStartTimeRaw { get; set; }
/// <summary>
/// Gets the scheduled end time of the scheduled event.
/// </summary>
[JsonIgnore]
public DateTimeOffset? ScheduledEndTime
=> !string.IsNullOrWhiteSpace(this.ScheduledEndTimeRaw) && DateTimeOffset.TryParse(this.ScheduledEndTimeRaw, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dto) ?
dto : null;
/// <summary>
/// Gets the scheduled end time of the scheduled event as raw string.
/// </summary>
[JsonProperty("scheduled_end_time", NullValueHandling = NullValueHandling.Ignore)]
internal string ScheduledEndTimeRaw { get; set; }
/// <summary>
/// Gets the privacy level of the scheduled event.
/// </summary>
[JsonProperty("privacy_level", NullValueHandling = NullValueHandling.Ignore)]
internal ScheduledEventPrivacyLevel PrivacyLevel { get; set; }
/// <summary>
/// Gets the status of the scheduled event.
/// </summary>
[JsonProperty("status", NullValueHandling = NullValueHandling.Ignore)]
public ScheduledEventStatus Status { get; internal set; }
/// <summary>
/// Gets the entity type.
/// </summary>
[JsonProperty("entity_type", NullValueHandling = NullValueHandling.Ignore)]
public ScheduledEventEntityType EntityType { get; internal set; }
/// <summary>
/// Gets id of the entity.
/// </summary>
[JsonProperty("entity_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? EntityId { get; internal set; }
/// <summary>
/// Gets metadata of the entity.
/// </summary>
[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.
/// <summary>
/// Gets the sku ids of the scheduled event.
/// </summary>
[JsonProperty("sku_ids", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<ulong> SkuIds { get; internal set; }*/
/// <summary>
/// Gets the total number of users subscribed to the scheduled event.
/// </summary>
[JsonProperty("user_count", NullValueHandling = NullValueHandling.Ignore)]
public int UserCount { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordScheduledEvent"/> class.
/// </summary>
internal DiscordScheduledEvent()
{ }
#region Methods
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Modifies the current scheduled event.
/// </summary>
/// <param name="action">Action to perform on this thread</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageEvents"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the event does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task ModifyAsync(Action<ScheduledEventEditModel> action)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
{
var mdl = new ScheduledEventEditModel();
action(mdl);
Optional<ulong?> channelId = null;
if (this.EntityType == ScheduledEventEntityType.External || mdl.EntityType != ScheduledEventEntityType.External)
channelId = mdl.Channel
.MapOrNull<ulong?>(c => c.Type != ChannelType.Voice && c.Type != ChannelType.Stage
? throw new ArgumentException("Channel needs to be a voice or stage channel.")
: c.Id);
var coverb64 = ImageTool.Base64FromStream(mdl.CoverImage);
var scheduledEndTime = Optional<DateTimeOffset>.None;
if (mdl.ScheduledEndTime.HasValue && 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, mdl.Description, mdl.EntityType, mdl.Status, coverb64, mdl.AuditLogReason);
}
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Starts the current scheduled event.
/// </summary>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageEvents"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the event does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordScheduledEvent> StartAsync(string reason = null)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> 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");
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Cancels the current scheduled event.
/// </summary>
/// <param name="reason">The audit log reason.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageEvents"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the event does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordScheduledEvent> CancelAsync(string reason = null)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> 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");
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Ends the current scheduled event.
/// </summary>
/// <param name="reason">The audit log reason.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageEvents"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the event does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordScheduledEvent> EndAsync(string reason = null)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> 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");
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Gets a list of users RSVP'd to the scheduled event.
/// </summary>
/// <param name="limit">The limit how many users to receive from the event. Defaults to 100. Max 100.</param>
/// <param name="before">Get results of <see cref="DiscordScheduledEventUser"/> before the given snowflake.</param>
/// <param name="after">Get results of <see cref="DiscordScheduledEventUser"/> after the given snowflake.</param>
/// <param name="withMember">Whether to include guild member data.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the correct permissions.</exception>
/// <exception cref="NotFoundException">Thrown when the event does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<IReadOnlyDictionary<ulong, DiscordScheduledEventUser>> GetUsersAsync(int? limit = null, ulong? before = null, ulong? after = null, bool? withMember = null)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> await this.Discord.ApiClient.GetGuildScheduledEventRspvUsersAsync(this.GuildId, this.Id, limit, before, after, withMember);
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Deletes a scheduled event.
/// </summary>
/// <param name="reason">The audit log reason.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageEvents"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the event does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task DeleteAsync(string reason = null)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> await this.Discord.ApiClient.DeleteGuildScheduledEventAsync(this.GuildId, this.Id, reason);
#endregion
/// <summary>
/// Checks whether this <see cref="DiscordScheduledEvent"/> is equal to another object.
/// </summary>
/// <param name="obj">Object to compare to.</param>
/// <returns>Whether the object is equal to this <see cref="DiscordScheduledEvent"/>.</returns>
public override bool Equals(object obj)
=> this.Equals(obj as DiscordScheduledEvent);
/// <summary>
/// Checks whether this <see cref="DiscordScheduledEvent"/> is equal to another <see cref="DiscordScheduledEvent"/>.
/// </summary>
/// <param name="e"><see cref="DiscordScheduledEvent"/> to compare to.</param>
/// <returns>Whether the <see cref="DiscordScheduledEvent"/> is equal to this <see cref="DiscordScheduledEvent"/>.</returns>
public bool Equals(DiscordScheduledEvent e)
=> e is not null && (ReferenceEquals(this, e) || this.Id == e.Id);
/// <summary>
/// Gets the hash code for this <see cref="DiscordScheduledEvent"/>.
/// </summary>
/// <returns>The hash code for this <see cref="DiscordScheduledEvent"/>.</returns>
public override int GetHashCode()
=> this.Id.GetHashCode();
/// <summary>
/// Gets whether the two <see cref="DiscordScheduledEvent"/> objects are equal.
/// </summary>
/// <param name="e1">First event to compare.</param>
/// <param name="e2">Second event to compare.</param>
/// <returns>Whether the two events are equal.</returns>
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);
}
/// <summary>
/// Gets whether the two <see cref="DiscordScheduledEvent"/> objects are not equal.
/// </summary>
/// <param name="e1">First event to compare.</param>
/// <param name="e2">Second event to compare.</param>
/// <returns>Whether the two events are not equal.</returns>
public static bool operator !=(DiscordScheduledEvent e1, DiscordScheduledEvent e2)
=> !(e1 == e2);
}
diff --git a/DisCatSharp/Entities/Guild/ScheduledEvent/DiscordScheduledEventEntityMetadata.cs b/DisCatSharp/Entities/Guild/ScheduledEvent/DiscordScheduledEventEntityMetadata.cs
index e487b4d66..dc2ba2d46 100644
--- a/DisCatSharp/Entities/Guild/ScheduledEvent/DiscordScheduledEventEntityMetadata.cs
+++ b/DisCatSharp/Entities/Guild/ScheduledEvent/DiscordScheduledEventEntityMetadata.cs
@@ -1,55 +1,55 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents an scheduled event.
/// </summary>
public class DiscordScheduledEventEntityMetadata
{
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// External location if event type is <see cref="ScheduledEventEntityType.External"/>.
/// </summary>
[JsonProperty("location", NullValueHandling = NullValueHandling.Ignore)]
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
public string Location { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordScheduledEventEntityMetadata"/> class.
/// </summary>
internal DiscordScheduledEventEntityMetadata()
{ }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordScheduledEventEntityMetadata"/> class.
/// </summary>
/// <param name="location">The location.</param>
public DiscordScheduledEventEntityMetadata(string location)
{
this.Location = location;
}
}
diff --git a/DisCatSharp/Entities/Guild/ScheduledEvent/DiscordScheduledEventUser.cs b/DisCatSharp/Entities/Guild/ScheduledEvent/DiscordScheduledEventUser.cs
index 2bfb499cc..15c4a4f11 100644
--- a/DisCatSharp/Entities/Guild/ScheduledEvent/DiscordScheduledEventUser.cs
+++ b/DisCatSharp/Entities/Guild/ScheduledEvent/DiscordScheduledEventUser.cs
@@ -1,123 +1,123 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Entities;
/// <summary>
/// The discord scheduled event user.
/// </summary>
public class DiscordScheduledEventUser : IEquatable<DiscordScheduledEventUser>
{
/// <summary>
/// Gets the client instance this object is tied to.
/// </summary>
[JsonIgnore]
internal BaseDiscordClient Discord { get; set; }
/// <summary>
/// Gets the user.
/// </summary>
[JsonProperty("user")]
public DiscordUser User { get; internal set; }
/// <summary>
/// Gets the member.
/// Only applicable when requested with `with_member`.
/// </summary>
[JsonProperty("member", NullValueHandling = NullValueHandling.Ignore)]
internal DiscordMember Member { get; set; }
/// <summary>
/// Gets the scheduled event.
/// </summary>
[JsonIgnore]
public DiscordScheduledEvent ScheduledEvent
=> this.Discord.Guilds.TryGetValue(this.GuildId, out var guild) && guild.ScheduledEvents.TryGetValue(this.EventId, out var scheduledEvent) ? scheduledEvent : null;
/// <summary>
/// Gets or sets the event id.
/// </summary>
[JsonProperty("guild_scheduled_event_id")]
internal ulong EventId { get; set; }
/// <summary>
/// Gets or sets the guild id.
/// </summary>
[JsonIgnore]
internal ulong GuildId { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordScheduledEventUser"/> class.
/// </summary>
internal DiscordScheduledEventUser()
{ }
/// <summary>
/// Checks whether this <see cref="DiscordScheduledEventUser"/> is equal to another object.
/// </summary>
/// <param name="obj">Object to compare to.</param>
/// <returns>Whether the object is equal to this <see cref="DiscordScheduledEventUser"/>.</returns>
public override bool Equals(object obj)
=> this.Equals(obj as DiscordScheduledEventUser);
/// <summary>
/// Checks whether this <see cref="DiscordScheduledEventUser"/> is equal to another <see cref="DiscordScheduledEventUser"/>.
/// </summary>
/// <param name="e"><see cref="DiscordScheduledEventUser"/> to compare to.</param>
/// <returns>Whether the <see cref="DiscordScheduledEventUser"/> is equal to this <see cref="DiscordScheduledEventUser"/>.</returns>
public bool Equals(DiscordScheduledEventUser e)
=> e is not null && (ReferenceEquals(this, e) || HashCode.Combine(this.User.Id, this.EventId) == HashCode.Combine(e.User.Id, e.EventId));
/// <summary>
/// Gets the hash code for this <see cref="DiscordScheduledEventUser"/>.
/// </summary>
/// <returns>The hash code for this <see cref="DiscordScheduledEventUser"/>.</returns>
public override int GetHashCode()
=> HashCode.Combine(this.User.Id, this.EventId);
/// <summary>
/// Gets whether the two <see cref="DiscordScheduledEventUser"/> objects are equal.
/// </summary>
/// <param name="e1">First event to compare.</param>
/// <param name="e2">Second event to compare.</param>
/// <returns>Whether the two events are equal.</returns>
public static bool operator ==(DiscordScheduledEventUser e1, DiscordScheduledEventUser e2)
{
var o1 = e1 as object;
var o2 = e2 as object;
return (o1 != null || o2 == null) && (o1 == null || o2 != null) && ((o1 == null && o2 == null) || HashCode.Combine(e1.User.Id, e1.EventId) == HashCode.Combine(e2.User.Id, e2.EventId));
}
/// <summary>
/// Gets whether the two <see cref="DiscordScheduledEventUser"/> objects are not equal.
/// </summary>
/// <param name="e1">First event to compare.</param>
/// <param name="e2">Second event to compare.</param>
/// <returns>Whether the two events are not equal.</returns>
public static bool operator !=(DiscordScheduledEventUser e1, DiscordScheduledEventUser e2)
=> !(e1 == e2);
}
diff --git a/DisCatSharp/Entities/Guild/Stage/DiscordStageInstance.cs b/DisCatSharp/Entities/Guild/Stage/DiscordStageInstance.cs
index 90b778d9e..5e161117f 100644
--- a/DisCatSharp/Entities/Guild/Stage/DiscordStageInstance.cs
+++ b/DisCatSharp/Entities/Guild/Stage/DiscordStageInstance.cs
@@ -1,118 +1,118 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Attributes;
using DisCatSharp.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a Stage instance.
/// </summary>
public class DiscordStageInstance : SnowflakeObject, IEquatable<DiscordStageInstance>
{
/// <summary>
/// Gets the guild id of the associated Stage channel.
/// </summary>
[JsonProperty("guild_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong GuildId { get; internal set; }
/// <summary>
/// Gets the guild to which this channel belongs.
/// </summary>
[JsonIgnore]
public DiscordGuild Guild
=> this.Discord.Guilds.TryGetValue(this.GuildId, out var guild) ? guild : null;
/// <summary>
/// Gets id of the associated Stage channel.
/// </summary>
[JsonProperty("channel_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong ChannelId { get; internal set; }
/// <summary>
/// Gets the topic of the Stage instance.
/// </summary>
[JsonProperty("topic", NullValueHandling = NullValueHandling.Ignore)]
public string Topic { get; internal set; }
/// <summary>
/// Gets the topic of the Stage instance.
/// </summary>
[JsonProperty("privacy_level", NullValueHandling = NullValueHandling.Ignore), DiscordDeprecated("Will be static due to the discovery removal."), Obsolete]
public StagePrivacyLevel PrivacyLevel { get; internal set; } = StagePrivacyLevel.GuildOnly;
/// <summary>
/// Gets whether or not stage discovery is disabled.
/// </summary>
[JsonProperty("discoverable_disabled", NullValueHandling = NullValueHandling.Ignore), DiscordDeprecated("Will be removed due to the discovery removal."), Obsolete]
public bool DiscoverableDisabled { get; internal set; }
/// <summary>
/// Checks whether this <see cref="DiscordStageInstance"/> is equal to another object.
/// </summary>
/// <param name="obj">Object to compare to.</param>
/// <returns>Whether the object is equal to this <see cref="DiscordStageInstance"/>.</returns>
public override bool Equals(object obj)
=> this.Equals(obj as DiscordStageInstance);
/// <summary>
/// Checks whether this <see cref="DiscordStageInstance"/> is equal to another <see cref="DiscordStageInstance"/>.
/// </summary>
/// <param name="e"><see cref="DiscordStageInstance"/> to compare to.</param>
/// <returns>Whether the <see cref="DiscordStageInstance"/> is equal to this <see cref="DiscordStageInstance"/>.</returns>
public bool Equals(DiscordStageInstance e)
=> e is not null && (ReferenceEquals(this, e) || this.Id == e.Id);
/// <summary>
/// Gets the hash code for this <see cref="DiscordStageInstance"/>.
/// </summary>
/// <returns>The hash code for this <see cref="DiscordStageInstance"/>.</returns>
public override int GetHashCode() => this.Id.GetHashCode();
/// <summary>
/// Gets whether the two <see cref="DiscordStageInstance"/> objects are equal.
/// </summary>
/// <param name="e1">First channel to compare.</param>
/// <param name="e2">Second channel to compare.</param>
/// <returns>Whether the two channels are equal.</returns>
public static bool operator ==(DiscordStageInstance e1, DiscordStageInstance 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);
}
/// <summary>
/// Gets whether the two <see cref="DiscordStageInstance"/> objects are not equal.
/// </summary>
/// <param name="e1">First channel to compare.</param>
/// <param name="e2">Second channel to compare.</param>
/// <returns>Whether the two channels are not equal.</returns>
public static bool operator !=(DiscordStageInstance e1, DiscordStageInstance e2)
=> !(e1 == e2);
}
diff --git a/DisCatSharp/Entities/Guild/ThreadAndForum/DiscordThreadChannel.cs b/DisCatSharp/Entities/Guild/ThreadAndForum/DiscordThreadChannel.cs
index 4058a0f73..77885d5c5 100644
--- a/DisCatSharp/Entities/Guild/ThreadAndForum/DiscordThreadChannel.cs
+++ b/DisCatSharp/Entities/Guild/ThreadAndForum/DiscordThreadChannel.cs
@@ -1,388 +1,388 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using DisCatSharp.Exceptions;
using DisCatSharp.Net.Models;
using DisCatSharp.Net.Serialization;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a discord thread channel.
/// </summary>
public class DiscordThreadChannel : DiscordChannel
{
/// <summary>
/// Gets ID of the owner that started this thread.
/// </summary>
[JsonProperty("owner_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong OwnerId { get; internal set; }
[JsonProperty("total_message_sent", DefaultValueHandling = DefaultValueHandling.Ignore)]
public int TotalMessagesSent { get; internal set; }
/// <summary>
/// Gets an approximate count of messages in a thread, stops counting at 50.
/// </summary>
[JsonProperty("message_count", NullValueHandling = NullValueHandling.Ignore)]
public int? MessageCount { get; internal set; }
/// <summary>
/// Gets an approximate count of users in a thread, stops counting at 50.
/// </summary>
[JsonProperty("member_count", NullValueHandling = NullValueHandling.Ignore)]
public int? MemberCount { get; internal set; }
/// <summary>
/// Represents the current member for this thread. This will have a value if the user has joined the thread.
/// </summary>
[JsonProperty("member", NullValueHandling = NullValueHandling.Ignore)]
public DiscordThreadChannelMember CurrentMember { get; internal set; }
/// <summary>
/// Gets the threads metadata.
/// </summary>
[JsonProperty("thread_metadata", NullValueHandling = NullValueHandling.Ignore)]
public DiscordThreadChannelMetadata ThreadMetadata { get; internal set; }
/// <summary>
/// Gets the thread members object.
/// </summary>
[JsonIgnore]
public IReadOnlyDictionary<ulong, DiscordThreadChannelMember> ThreadMembers => new ReadOnlyConcurrentDictionary<ulong, DiscordThreadChannelMember>(this.ThreadMembersInternal);
[JsonProperty("thread_member", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary<ulong, DiscordThreadChannelMember> ThreadMembersInternal;
/// <summary>
/// List of applied tag ids.
/// </summary>
[JsonIgnore]
internal IReadOnlyList<ulong> AppliedTagIds
=> this.AppliedTagIdsInternal;
/// <summary>
/// List of applied tag ids.
/// </summary>
[JsonProperty("applied_tags", NullValueHandling = NullValueHandling.Ignore)]
internal List<ulong> AppliedTagIdsInternal;
/// <summary>
/// Gets the list of applied tags.
/// Only applicable for forum channel posts.
/// </summary>
[JsonIgnore]
public IReadOnlyList<ForumPostTag> AppliedTags
=> this.AppliedTagIds?.Select(id => this.Parent.GetForumPostTag(id)).Where(x => x != null).ToList();
/// <summary>
/// Initializes a new instance of the <see cref="DiscordThreadChannel"/> class.
/// </summary>
internal DiscordThreadChannel()
{ }
#region Methods
/// <summary>
/// Modifies the current thread.
/// </summary>
/// <param name="action">Action to perform on this thread</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageThreads"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the thread does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
/// <exception cref="NotSupportedException">Thrown when the <see cref="ThreadAutoArchiveDuration"/> cannot be modified. This happens, when the guild hasn't reached a certain boost <see cref="PremiumTier"/>.</exception>
public Task ModifyAsync(Action<ThreadEditModel> action)
{
var mdl = new ThreadEditModel();
action(mdl);
return this.Parent.Type == ChannelType.Forum && mdl.AppliedTags.HasValue && mdl.AppliedTags.Value.Count() > 5
? throw new NotSupportedException("Cannot have more than 5 applied tags.")
: this.Discord.ApiClient.ModifyThreadAsync(this.Id, this.Parent.Type, mdl.Name, mdl.Locked, mdl.Archived, mdl.PerUserRateLimit, mdl.AutoArchiveDuration, mdl.Invitable, mdl.AppliedTags, mdl.Pinned, mdl.AuditLogReason);
}
/// <summary>
/// Add a tag to the current thread.
/// </summary>
/// <param name="tag">The tag to add.</param>
/// <param name="reason">The reason for the audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageThreads"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the thread does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task AddTagAsync(ForumPostTag tag, string reason = null)
=> this.AppliedTagIds.Count == 5 ?
throw new NotSupportedException("Cannot have more than 5 applied tags.") :
this.Discord.ApiClient.ModifyThreadAsync(this.Id, this.Parent.Type, null, null, null, null, null, null, new List<ForumPostTag>(this.AppliedTags) { tag }, null, reason);
/// <summary>
/// Remove a tag from the current thread.
/// </summary>
/// <param name="tag">The tag to remove.</param>
/// <param name="reason">The reason for the audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageThreads"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the thread does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task RemoveTagAsync(ForumPostTag tag, string reason = null)
=> await this.Discord.ApiClient.ModifyThreadAsync(this.Id, this.Parent.Type, null, null, null, null, null, null, new List<ForumPostTag>(this.AppliedTags).Where(x => x != tag).ToList(), null, reason);
/// <summary>
/// Archives a thread.
/// </summary>
/// <param name="locked">Whether the thread should be locked.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageThreads"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the thread does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task ArchiveAsync(bool locked = true, string reason = null)
=> this.Discord.ApiClient.ModifyThreadAsync(this.Id, this.Parent.Type, null, locked, true, null, null, null, null, null, reason);
/// <summary>
/// Unarchives a thread.
/// </summary>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="NotFoundException">Thrown when the thread does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task UnarchiveAsync(string reason = null)
=> this.Discord.ApiClient.ModifyThreadAsync(this.Id, this.Parent.Type, null, null, false, null, null, null, null, null, reason);
/// <summary>
/// Gets the members of a thread. Needs the <see cref="DiscordIntents.GuildMembers"/> intent.
/// </summary>
/// <param name="withMember">Whether to request the member object. (If set to true, will paginate the result)</param>
/// <param name="after">Request all members after the specified. (Currently only utilized if withMember is set to true)</param>
/// <param name="limit">The amount of members to fetch. (Currently only utilized if withMember is set to true)</param>
/// <exception cref="NotFoundException">Thrown when the thread does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<IReadOnlyList<DiscordThreadChannelMember>> GetMembersAsync(bool withMember = false, ulong? after = null, int? limit = null)
=> await this.Discord.ApiClient.GetThreadMembersAsync(this.Id);
/// <summary>
/// Adds a member to this thread.
/// </summary>
/// <param name="memberId">The member id to be added.</param>
/// <exception cref="NotFoundException">Thrown when the thread does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task AddMemberAsync(ulong memberId)
=> this.Discord.ApiClient.AddThreadMemberAsync(this.Id, memberId);
/// <summary>
/// Adds a member to this thread.
/// </summary>
/// <param name="member">The member to be added.</param>
/// <exception cref="NotFoundException">Thrown when the thread does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task AddMemberAsync(DiscordMember member)
=> this.AddMemberAsync(member.Id);
/// <summary>
/// Gets a member in this thread.
/// </summary>
/// <param name="memberId">The id of the member to get.</param>
/// <param name="withMember">Whether to request the member object.</param>
/// <exception cref="NotFoundException">Thrown when the member is not part of the thread.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordThreadChannelMember> GetMemberAsync(ulong memberId, bool withMember = false)
=> this.Discord.ApiClient.GetThreadMemberAsync(this.Id, memberId, withMember);
/// <summary>
/// Tries to get a member in this thread.
/// </summary>
/// <param name="memberId">The id of the member to get.</param>
/// <param name="withMember">Whether to request the member object.</param>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public async Task<DiscordThreadChannelMember?> TryGetMemberAsync(ulong memberId, bool withMember = false)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
try
{
return await this.GetMemberAsync(memberId, withMember).ConfigureAwait(false);
}
catch (NotFoundException)
{
return null;
}
}
/// <summary>
/// Gets a member in this thread.
/// </summary>
/// <param name="member">The member to get.</param>
/// <param name="withMember">Whether to request the member object.</param>
/// <exception cref="NotFoundException">Thrown when the member is not part of the thread.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordThreadChannelMember> GetMemberAsync(DiscordMember member, bool withMember = false)
=> this.Discord.ApiClient.GetThreadMemberAsync(this.Id, member.Id, withMember);
/// <summary>
/// Tries to get a member in this thread.
/// </summary>
/// <param name="member">The member to get.</param>
/// <param name="withMember">Whether to request the member object.</param>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public async Task<DiscordThreadChannelMember?> TryGetMemberAsync(DiscordMember member, bool withMember = false)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
try
{
return await this.GetMemberAsync(member, withMember).ConfigureAwait(false);
}
catch (NotFoundException)
{
return null;
}
}
/// <summary>
/// Removes a member from this thread.
/// </summary>
/// <param name="memberId">The member id to be removed.</param>
/// <exception cref="NotFoundException">Thrown when the thread does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task RemoveMemberAsync(ulong memberId)
=> this.Discord.ApiClient.RemoveThreadMemberAsync(this.Id, memberId);
/// <summary>
/// Removes a member from this thread. Only applicable to private threads.
/// </summary>
/// <param name="member">The member to be removed.</param>
/// <exception cref="NotFoundException">Thrown when the thread does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task RemoveMemberAsync(DiscordMember member)
=> this.RemoveMemberAsync(member.Id);
/// <summary>
/// Adds a role to this thread. Only applicable to private threads.
/// </summary>
/// <param name="roleId">The role id to be added.</param>
/// <exception cref="NotFoundException">Thrown when the thread does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task AddRoleAsync(ulong roleId)
{
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);
}
}
/// <summary>
/// Adds a role to this thread. Only applicable to private threads.
/// </summary>
/// <param name="role">The role to be added.</param>
/// <exception cref="NotFoundException">Thrown when the thread does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task AddRoleAsync(DiscordRole role)
=> this.AddRoleAsync(role.Id);
/// <summary>
/// Removes a role from this thread. Only applicable to private threads.
/// </summary>
/// <param name="roleId">The role id to be removed.</param>
/// <exception cref="NotFoundException">Thrown when the thread does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task RemoveRoleAsync(ulong roleId)
{
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);
}
}
/// <summary>
/// Removes a role from this thread. Only applicable to private threads.
/// </summary>
/// <param name="role">The role to be removed.</param>
/// <exception cref="NotFoundException">Thrown when the thread does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task RemoveRoleAsync(DiscordRole role)
=> this.RemoveRoleAsync(role.Id);
/// <summary>
/// Joins a thread.
/// </summary>
/// <exception cref="UnauthorizedException">Thrown when the client has no access to this thread.</exception>
/// <exception cref="NotFoundException">Thrown when the thread does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task JoinAsync()
=> this.Discord.ApiClient.JoinThreadAsync(this.Id);
/// <summary>
/// Leaves a thread.
/// </summary>
/// <exception cref="UnauthorizedException">Thrown when the client has no access to this thread.</exception>
/// <exception cref="NotFoundException">Thrown when the thread does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task LeaveAsync()
=> this.Discord.ApiClient.LeaveThreadAsync(this.Id);
/// <summary>
/// Returns a string representation of this thread.
/// </summary>
/// <returns>String representation of this thread.</returns>
public override string ToString()
=> 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})",
};
#endregion
}
diff --git a/DisCatSharp/Entities/Guild/ThreadAndForum/DiscordThreadChannelMember.cs b/DisCatSharp/Entities/Guild/ThreadAndForum/DiscordThreadChannelMember.cs
index 367fbf0af..5d4bbe4a1 100644
--- a/DisCatSharp/Entities/Guild/ThreadAndForum/DiscordThreadChannelMember.cs
+++ b/DisCatSharp/Entities/Guild/ThreadAndForum/DiscordThreadChannelMember.cs
@@ -1,143 +1,143 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a discord thread member object.
/// </summary>
public class DiscordThreadChannelMember : SnowflakeObject, IEquatable<DiscordThreadChannelMember>
{
/// <summary>
/// Gets the id of the user.
/// </summary>
[JsonProperty("user_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong UserId { get; internal set; }
/// <summary>
/// Gets the member object of the user.
/// </summary>
[JsonProperty("member", NullValueHandling = NullValueHandling.Ignore)]
public DiscordMember Member { get; internal set; }
/// <summary>
/// Gets the presence of the user.
/// </summary>
[JsonProperty("presence", NullValueHandling = NullValueHandling.Ignore)]
public DiscordPresence Presence { get; internal set; }
/// <summary>
/// Gets the timestamp when the user joined the thread.
/// </summary>
[JsonIgnore]
public DateTimeOffset? JoinTimeStamp
=> !string.IsNullOrWhiteSpace(this.JoinTimeStampRaw) && DateTimeOffset.TryParse(this.JoinTimeStampRaw, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dto) ?
dto : null;
/// <summary>
/// Gets the timestamp when the user joined the thread as raw string.
/// </summary>
[JsonProperty("join_timestamp", NullValueHandling = NullValueHandling.Ignore)]
internal string JoinTimeStampRaw { get; set; }
/// <summary>
/// Gets the thread member flags.
/// </summary>
[JsonProperty("flags", NullValueHandling = NullValueHandling.Ignore)]
public ThreadMemberFlags Flags { get; internal set; }
/// <summary>
/// Gets the category that contains this channel. For threads, gets the channel this thread was created in.
/// </summary>
[JsonIgnore]
public DiscordChannel Thread
=> this.Guild != null && this.Guild.ThreadsInternal.TryGetValue(this.Id, out var thread) ? thread : null;
/// <summary>
/// Gets the guild to which this channel belongs.
/// </summary>
[JsonIgnore]
public DiscordGuild Guild
=> this.Discord.Guilds.TryGetValue(this.GuildId, out var guild) ? guild : null;
[JsonIgnore]
internal ulong GuildId;
/// <summary>
/// Checks whether this <see cref="DiscordThreadChannelMember"/> is equal to another object.
/// </summary>
/// <param name="obj">Object to compare to.</param>
/// <returns>Whether the object is equal to this <see cref="DiscordThreadChannelMember"/>.</returns>
public override bool Equals(object obj)
=> this.Equals(obj as DiscordThreadChannelMember);
/// <summary>
/// Checks whether this <see cref="DiscordThreadChannel"/> is equal to another <see cref="DiscordThreadChannelMember"/>.
/// </summary>
/// <param name="e"><see cref="DiscordThreadChannel"/> to compare to.</param>
/// <returns>Whether the <see cref="DiscordThreadChannel"/> is equal to this <see cref="DiscordThreadChannelMember"/>.</returns>
public bool Equals(DiscordThreadChannelMember e)
=> e is not null && (ReferenceEquals(this, e) || (this.Id == e.Id && this.UserId == e.UserId));
/// <summary>
/// Gets the hash code for this <see cref="DiscordThreadChannelMember"/>.
/// </summary>
/// <returns>The hash code for this <see cref="DiscordThreadChannelMember"/>.</returns>
public override int GetHashCode()
=> HashCode.Combine(this.Id.GetHashCode(), this.UserId.GetHashCode());
/// <summary>
/// Gets whether the two <see cref="DiscordThreadChannel"/> objects are equal.
/// </summary>
/// <param name="e1">First channel to compare.</param>
/// <param name="e2">Second channel to compare.</param>
/// <returns>Whether the two channels are equal.</returns>
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));
}
/// <summary>
/// Gets whether the two <see cref="DiscordThreadChannelMember"/> objects are not equal.
/// </summary>
/// <param name="e1">First channel to compare.</param>
/// <param name="e2">Second channel to compare.</param>
/// <returns>Whether the two channels are not equal.</returns>
public static bool operator !=(DiscordThreadChannelMember e1, DiscordThreadChannelMember e2)
=> !(e1 == e2);
/// <summary>
/// Initializes a new instance of the <see cref="DiscordThreadChannelMember"/> class.
/// </summary>
internal DiscordThreadChannelMember()
{ }
}
diff --git a/DisCatSharp/Entities/Guild/ThreadAndForum/DiscordThreadChannelMetadata.cs b/DisCatSharp/Entities/Guild/ThreadAndForum/DiscordThreadChannelMetadata.cs
index 7915bd432..8014794d6 100644
--- a/DisCatSharp/Entities/Guild/ThreadAndForum/DiscordThreadChannelMetadata.cs
+++ b/DisCatSharp/Entities/Guild/ThreadAndForum/DiscordThreadChannelMetadata.cs
@@ -1,102 +1,102 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a discord thread metadata object.
/// </summary>
public class DiscordThreadChannelMetadata
{
/// <summary>
/// Gets whether the thread is archived or not.
/// </summary>
[JsonProperty("archived", NullValueHandling = NullValueHandling.Ignore)]
public bool Archived { get; internal set; }
/// <summary>
/// Gets ID of the archiver.
/// </summary>
[JsonProperty("archiver_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? Archiver { get; internal set; }
/// <summary>
/// Gets the time when it will be archived, while there is no action inside the thread (In minutes).
/// </summary>
[JsonProperty("auto_archive_duration", NullValueHandling = NullValueHandling.Ignore)]
public ThreadAutoArchiveDuration AutoArchiveDuration { get; internal set; }
/// <summary>
/// Gets the timestamp when it was archived.
/// </summary>
[JsonIgnore]
public DateTimeOffset? ArchiveTimestamp
=> !string.IsNullOrWhiteSpace(this.ArchiveTimestampRaw) && DateTimeOffset.TryParse(this.ArchiveTimestampRaw, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dto) ?
dto : null;
/// <summary>
/// Gets the timestamp when it was archived as raw string.
/// </summary>
[JsonProperty("archive_timestamp", NullValueHandling = NullValueHandling.Ignore)]
internal string ArchiveTimestampRaw { get; set; }
/// <summary>
/// Gets whether the thread is locked.
/// </summary>
[JsonProperty("locked", NullValueHandling = NullValueHandling.Ignore)]
public bool? Locked { get; internal set; }
/// <summary>
/// Gets whether non-moderators can add other non-moderators to a thread; only available on private threads.
/// </summary>
[JsonProperty("invitable", NullValueHandling = NullValueHandling.Ignore)]
public bool? Invitable { get; internal set; }
/// <summary>
/// Gets the timestamp when the thread was created.
/// Only populated for threads created after 2022-01-09.
/// </summary>
[JsonIgnore]
public DateTimeOffset? CreateTimestamp
=> !string.IsNullOrWhiteSpace(this.CreateTimestampRaw) && DateTimeOffset.TryParse(this.CreateTimestampRaw, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dto) ?
dto : null;
/// <summary>
/// Gets the timestamp when the thread was created as raw string.
/// Only populated for threads created after 2022-01-09.
/// </summary>
[JsonProperty("create_timestamp", NullValueHandling = NullValueHandling.Ignore)]
internal string CreateTimestampRaw { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordThreadChannelMetadata"/> class.
/// </summary>
internal DiscordThreadChannelMetadata()
{ }
}
diff --git a/DisCatSharp/Entities/Guild/ThreadAndForum/DiscordThreadResult.cs b/DisCatSharp/Entities/Guild/ThreadAndForum/DiscordThreadResult.cs
index 26e42e810..4987f14fa 100644
--- a/DisCatSharp/Entities/Guild/ThreadAndForum/DiscordThreadResult.cs
+++ b/DisCatSharp/Entities/Guild/ThreadAndForum/DiscordThreadResult.cs
@@ -1,61 +1,61 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a discord thread result.
/// </summary>
public class DiscordThreadResult
{
/// <summary>
/// Gets the returned threads.
/// </summary>
[JsonIgnore]
public Dictionary<ulong, DiscordThreadChannel> ReturnedThreads
=> this.Threads == null || !this.Threads.Any() ? new Dictionary<ulong, DiscordThreadChannel>() : this.Threads.Select(t => new { t.Id, t }).ToDictionary(t => t.Id, t => t.t);
[JsonProperty("threads", NullValueHandling = NullValueHandling.Ignore)]
internal List<DiscordThreadChannel> Threads { get; set; }
/// <summary>
/// Gets the active members.
/// </summary>
[JsonProperty("members", NullValueHandling = NullValueHandling.Ignore)]
public List<DiscordThreadChannelMember> ActiveMembers { get; internal set; }
/// <summary>
/// Whether there are more results.
/// </summary>
public bool HasMore { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordThreadResult"/> class.
/// </summary>
internal DiscordThreadResult()
: base()
{ }
}
diff --git a/DisCatSharp/Entities/Guild/ThreadAndForum/ForumPostTag.cs b/DisCatSharp/Entities/Guild/ThreadAndForum/ForumPostTag.cs
index 99daaf9a9..b565520ae 100644
--- a/DisCatSharp/Entities/Guild/ThreadAndForum/ForumPostTag.cs
+++ b/DisCatSharp/Entities/Guild/ThreadAndForum/ForumPostTag.cs
@@ -1,182 +1,182 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
using System.Threading.Tasks;
using DisCatSharp.Net.Models;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a discord forum post tag.
/// </summary>
public class ForumPostTag : NullableSnowflakeObject, IEquatable<ForumPostTag>
{
/// <summary>
/// Gets the channel id this tag belongs to.
/// </summary>
[JsonIgnore]
internal ulong ChannelId;
/// <summary>
/// Gets the channel this tag belongs to.
/// </summary>
[JsonIgnore]
internal DiscordChannel Channel;
/// <summary>
/// Gets the name of this forum post tag.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; internal set; }
/// <summary>
/// Gets the emoji id of the forum post tag.
/// </summary>
[JsonProperty("emoji_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? EmojiId { get; internal set; }
/// <summary>
/// Gets the unicode emoji of the forum post tag.
/// </summary>
[JsonProperty("emoji_name", NullValueHandling = NullValueHandling.Include)]
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public string? UnicodeEmojiString { get; internal set; }
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
/// <summary>
/// Gets whether the tag can only be used by moderators.
/// </summary>
[JsonProperty("moderated", NullValueHandling = NullValueHandling.Ignore)]
public bool? Moderated { get; internal set; }
/// <summary>
/// Gets the emoji.
/// </summary>
[JsonIgnore]
public DiscordEmoji Emoji
=> this.UnicodeEmojiString != null ? DiscordEmoji.FromName(this.Discord, $":{this.UnicodeEmojiString}:", false) : DiscordEmoji.FromGuildEmote(this.Discord, this.EmojiId.Value);
/// <summary>
/// Initializes a new instance of the <see cref="ForumPostTag"/> class.
/// </summary>
internal ForumPostTag()
{ }
/// <summary>
/// Initializes a new instance of the <see cref="ForumPostTag"/> class.
/// </summary>
/// <param name="name">The tags name.</param>
/// <param name="emojiId">The tags emoji id. Defaults to <see langword="null"/>.</param>
/// <param name="emojiName">The tags emoji name (unicode emoji). Defaults to <see langword="null"/>.</param>
/// <param name="moderated">Whether this tag can only be applied by moderators. Defaults to <see langword="false"/>.</param>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public ForumPostTag(string name, ulong? emojiId = null, string? emojiName = null, bool moderated = false)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
this.Id = null;
this.Name = name;
this.EmojiId = emojiId;
this.UnicodeEmojiString = emojiName;
this.Moderated = moderated;
}
/// <summary>
/// Modifies the tag.
/// </summary>
/// <exception cref="NotImplementedException">This method is currently not implemented.</exception>
public async Task<ForumPostTag> ModifyAsync(Action<ForumPostTagEditModel> action)
{
var mdl = new ForumPostTagEditModel();
action(mdl);
var res = await this.Discord.ApiClient.ModifyForumChannelAsync(this.ChannelId, null, null, null, null, null, null, this.Channel.InternalAvailableTags.Where(x => x.Id != this.Id).ToList().Append(new ForumPostTag()
{
Id = this.Id,
Discord = this.Discord,
ChannelId = this.ChannelId,
Channel = this.Channel,
EmojiId = mdl.Emoji.HasValue ? mdl.Emoji.Value.Id : this.EmojiId,
Moderated = mdl.Moderated.HasValue ? mdl.Moderated.Value : this.Moderated,
Name = mdl.Name.HasValue ? mdl.Name.Value : this.Name,
UnicodeEmojiString = mdl.Emoji.HasValue ? mdl.Emoji.Value.Name : this.UnicodeEmojiString
}).ToList(), null, null, null, null, null, null, null, mdl.AuditLogReason);
return res.InternalAvailableTags.First(x => x.Id == this.Id);
}
/// <summary>
/// Deletes the tag.
/// </summary>
/// <exception cref="NotImplementedException">This method is currently not implemented.</exception>
public Task DeleteAsync(string reason = null)
=> this.Discord.ApiClient.ModifyForumChannelAsync(this.ChannelId, null, null, Optional.None, Optional.None, null, Optional.None, this.Channel.InternalAvailableTags.Where(x => x.Id != this.Id).ToList(), Optional.None, Optional.None, Optional.None, Optional.None, Optional.None, null, Optional.None, reason);
/// <summary>
/// Checks whether this <see cref="ForumPostTag"/> is equal to another object.
/// </summary>
/// <param name="obj">Object to compare to.</param>
/// <returns>Whether the object is equal to this <see cref="ForumPostTag"/>.</returns>
public override bool Equals(object obj)
=> this.Equals(obj as ForumPostTag);
/// <summary>
/// Checks whether this <see cref="ForumPostTag"/> is equal to another <see cref="ForumPostTag"/>.
/// </summary>
/// <param name="e"><see cref="ForumPostTag"/> to compare to.</param>
/// <returns>Whether the <see cref="ForumPostTag"/> is equal to this <see cref="ForumPostTag"/>.</returns>
public bool Equals(ForumPostTag e)
=> e is not null && (ReferenceEquals(this, e) || (this.Id == e.Id && this.Name == e.Name));
/// <summary>
/// Gets the hash code for this <see cref="ForumPostTag"/>.
/// </summary>
/// <returns>The hash code for this <see cref="ForumPostTag"/>.</returns>
public override int GetHashCode()
=> this.Id.GetHashCode();
/// <summary>
/// Gets whether the two <see cref="ForumPostTag"/> objects are equal.
/// </summary>
/// <param name="e1">First forum post tag to compare.</param>
/// <param name="e2">Second forum post tag to compare.</param>
/// <returns>Whether the two forum post tags are equal.</returns>
public static bool operator ==(ForumPostTag e1, ForumPostTag 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);
}
/// <summary>
/// Gets whether the two <see cref="DiscordEmoji"/> objects are not equal.
/// </summary>
/// <param name="e1">First forum post tag to compare.</param>
/// <param name="e2">Second forum post tag to compare.</param>
/// <returns>Whether the two forum post tags are not equal.</returns>
public static bool operator !=(ForumPostTag e1, ForumPostTag e2)
=> !(e1 == e2);
}
diff --git a/DisCatSharp/Entities/Guild/ThreadAndForum/ForumReactionEmoji.cs b/DisCatSharp/Entities/Guild/ThreadAndForum/ForumReactionEmoji.cs
index a645495ed..bc51d24b0 100644
--- a/DisCatSharp/Entities/Guild/ThreadAndForum/ForumReactionEmoji.cs
+++ b/DisCatSharp/Entities/Guild/ThreadAndForum/ForumReactionEmoji.cs
@@ -1,59 +1,59 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
public class ForumReactionEmoji
{
/// <summary>
/// Creates a new forum reaction emoji.
/// Use either <paramref name="emojiId"/> or <paramref name="unicodeEmojiString"/>. Not both.
/// </summary>
/// <param name="emojiId">The emoji id. Has to be from the same server.</param>
/// <param name="unicodeEmojiString">The unicode emoji.</param>
public ForumReactionEmoji(ulong? emojiId = null, string unicodeEmojiString = null)
{
this.EmojiId = emojiId;
this.EmojiName = unicodeEmojiString;
}
/// <summary>
/// Gets the emoji id of the forum post tag.
/// </summary>
[JsonProperty("emoji_id", NullValueHandling = NullValueHandling.Include)]
public ulong? EmojiId { get; internal set; }
/// <summary>
/// Gets the unicode emoji of the forum post tag.
/// </summary>
[JsonProperty("emoji_name", NullValueHandling = NullValueHandling.Include)]
public string EmojiName { get; internal set; }
/// <summary>
/// Gets the emoji.
/// </summary>
public DiscordEmoji GetEmoji(DiscordClient client)
=> this.EmojiName != null ? DiscordEmoji.FromName(client, $":{this.EmojiName}:", false) : DiscordEmoji.FromGuildEmote(client, this.EmojiId.Value);
}
diff --git a/DisCatSharp/Entities/Guild/Widget/DiscordWidget.cs b/DisCatSharp/Entities/Guild/Widget/DiscordWidget.cs
index 90a9e0fc1..6fe0d1ea2 100644
--- a/DisCatSharp/Entities/Guild/Widget/DiscordWidget.cs
+++ b/DisCatSharp/Entities/Guild/Widget/DiscordWidget.cs
@@ -1,69 +1,69 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a Discord guild's widget.
/// </summary>
public class DiscordWidget : SnowflakeObject
{
/// <summary>
/// Gets the guild.
/// </summary>
[JsonIgnore]
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the guild's name.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; internal set; }
/// <summary>
/// Gets the guild's invite URL.
/// </summary>
[JsonProperty("instant_invite", NullValueHandling = NullValueHandling.Ignore)]
public string InstantInviteUrl { get; internal set; }
/// <summary>
/// Gets the number of online members.
/// </summary>
[JsonProperty("presence_count", NullValueHandling = NullValueHandling.Ignore)]
public int PresenceCount { get; internal set; }
/// <summary>
/// Gets a list of online members.
/// </summary>
[JsonProperty("members", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<DiscordWidgetMember> Members { get; internal set; }
/// <summary>
/// Gets a list of widget channels.
/// </summary>
[JsonIgnore]
public IReadOnlyList<DiscordChannel> Channels { get; internal set; }
}
diff --git a/DisCatSharp/Entities/Guild/Widget/DiscordWidgetMember.cs b/DisCatSharp/Entities/Guild/Widget/DiscordWidgetMember.cs
index dec35d4bf..fddf75d35 100644
--- a/DisCatSharp/Entities/Guild/Widget/DiscordWidgetMember.cs
+++ b/DisCatSharp/Entities/Guild/Widget/DiscordWidgetMember.cs
@@ -1,67 +1,67 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a member within a Discord guild's widget.
/// </summary>
public class DiscordWidgetMember
{
/// <summary>
/// Gets the member's identifier within the widget.
/// </summary>
[JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)]
public ulong Id { get; internal set; }
/// <summary>
/// Gets the member's username.
/// </summary>
[JsonProperty("username", NullValueHandling = NullValueHandling.Ignore)]
public string Username { get; internal set; }
/// <summary>
/// Gets the member's discriminator.
/// </summary>
[JsonProperty("discriminator", NullValueHandling = NullValueHandling.Ignore)]
public string Discriminator { get; internal set; }
/// <summary>
/// Gets the member's avatar.
/// </summary>
[JsonProperty("avatar", NullValueHandling = NullValueHandling.Ignore)]
public string Avatar { get; internal set; }
/// <summary>
/// Gets the member's online status.
/// </summary>
[JsonProperty("status", NullValueHandling = NullValueHandling.Ignore)]
public string Status { get; internal set; }
/// <summary>
/// Gets the member's avatar url.
/// </summary>
[JsonProperty("avatar_url", NullValueHandling = NullValueHandling.Ignore)]
public string AvatarUrl { get; internal set; }
}
diff --git a/DisCatSharp/Entities/Guild/Widget/DiscordWidgetSettings.cs b/DisCatSharp/Entities/Guild/Widget/DiscordWidgetSettings.cs
index e10acbc23..7f72b6a1f 100644
--- a/DisCatSharp/Entities/Guild/Widget/DiscordWidgetSettings.cs
+++ b/DisCatSharp/Entities/Guild/Widget/DiscordWidgetSettings.cs
@@ -1,54 +1,54 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a Discord guild's widget settings.
/// </summary>
public class DiscordWidgetSettings
{
/// <summary>
/// Gets the guild.
/// </summary>
internal DiscordGuild Guild { get; set; }
/// <summary>
/// Gets the guild's widget channel id.
/// </summary>
[JsonProperty("channel_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong ChannelId { get; internal set; }
/// <summary>
/// Gets the guild's widget channel.
/// </summary>
public DiscordChannel Channel
=> this.Guild?.GetChannel(this.ChannelId);
/// <summary>
/// Whether if the guild's widget is enabled.
/// </summary>
[JsonProperty("enabled", NullValueHandling = NullValueHandling.Ignore)]
public bool IsEnabled { get; internal set; }
}
diff --git a/DisCatSharp/Entities/Integration/DiscordIntegration.cs b/DisCatSharp/Entities/Integration/DiscordIntegration.cs
index ba51523de..a3bcaea70 100644
--- a/DisCatSharp/Entities/Integration/DiscordIntegration.cs
+++ b/DisCatSharp/Entities/Integration/DiscordIntegration.cs
@@ -1,119 +1,119 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a Discord integration. These appear on the profile as linked 3rd party accounts.
/// </summary>
public class DiscordIntegration : SnowflakeObject
{
/// <summary>
/// Gets the integration name.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; internal set; }
/// <summary>
/// Gets the integration type.
/// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
public string Type { get; internal set; }
/// <summary>
/// Gets whether this integration is enabled.
/// </summary>
[JsonProperty("enabled", NullValueHandling = NullValueHandling.Ignore)]
public bool IsEnabled { get; internal set; }
/// <summary>
/// Gets whether this integration is syncing.
/// </summary>
[JsonProperty("syncing", NullValueHandling = NullValueHandling.Ignore)]
public bool IsSyncing { get; internal set; }
/// <summary>
/// Gets ID of the role this integration uses for subscribers.
/// </summary>
[JsonProperty("role_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong RoleId { get; internal set; }
/// <summary>
/// Gets the expiration behaviour.
/// </summary>
[JsonProperty("expire_behavior", NullValueHandling = NullValueHandling.Ignore)]
public IntegrationExpireBehavior ExpireBehavior { get; internal set; }
/// <summary>
/// Gets the grace period before expiring subscribers.
/// </summary>
[JsonProperty("expire_grace_period", NullValueHandling = NullValueHandling.Ignore)]
public int ExpireGracePeriod { get; internal set; }
/// <summary>
/// Gets the user that owns this integration.
/// </summary>
[JsonProperty("user", NullValueHandling = NullValueHandling.Ignore)]
public DiscordUser User { get; internal set; }
/// <summary>
/// Gets the 3rd party service account for this integration.
/// </summary>
[JsonProperty("account", NullValueHandling = NullValueHandling.Ignore)]
public DiscordIntegrationAccount Account { get; internal set; }
/// <summary>
/// Gets the date and time this integration was last synced.
/// </summary>
[JsonProperty("synced_at", NullValueHandling = NullValueHandling.Ignore)]
public DateTimeOffset SyncedAt { get; internal set; }
/// <summary>
/// Gets the subscriber count.
/// </summary>
[JsonProperty("subscriber_count", NullValueHandling = NullValueHandling.Ignore)]
public int? SubscriberCount { get; internal set; }
/// <summary>
/// Whether the integration is revoked.
/// </summary>
[JsonProperty("revoked", NullValueHandling = NullValueHandling.Ignore)]
public bool? Revoked { get; internal set; }
[JsonProperty("application", NullValueHandling = NullValueHandling.Ignore)]
public DiscordApplication Application { get; internal set; }
[JsonProperty("scopes", NullValueHandling = NullValueHandling.Ignore)]
public string[] Scopes { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordIntegration"/> class.
/// </summary>
internal DiscordIntegration()
{ }
}
diff --git a/DisCatSharp/Entities/Integration/DiscordIntegrationAccount.cs b/DisCatSharp/Entities/Integration/DiscordIntegrationAccount.cs
index 38a780870..eb06e492c 100644
--- a/DisCatSharp/Entities/Integration/DiscordIntegrationAccount.cs
+++ b/DisCatSharp/Entities/Integration/DiscordIntegrationAccount.cs
@@ -1,49 +1,49 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a Discord integration account.
/// </summary>
public class DiscordIntegrationAccount
{
/// <summary>
/// Gets the ID of the account.
/// </summary>
[JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)]
public string Id { get; internal set; }
/// <summary>
/// Gets the name of the account.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordIntegrationAccount"/> class.
/// </summary>
internal DiscordIntegrationAccount()
{ }
}
diff --git a/DisCatSharp/Entities/Interaction/Components/Button/DiscordButtonComponent.cs b/DisCatSharp/Entities/Interaction/Components/Button/DiscordButtonComponent.cs
index 3be8fb42d..32d7ae07d 100644
--- a/DisCatSharp/Entities/Interaction/Components/Button/DiscordButtonComponent.cs
+++ b/DisCatSharp/Entities/Interaction/Components/Button/DiscordButtonComponent.cs
@@ -1,127 +1,127 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a button that can be pressed. Fires <see cref="DisCatSharp.DiscordClient.ComponentInteractionCreated"/> event when pressed.
/// </summary>
public sealed class DiscordButtonComponent : DiscordComponent
{
/// <summary>
/// The style of the button.
/// </summary>
[JsonProperty("style", NullValueHandling = NullValueHandling.Ignore)]
public ButtonStyle Style { get; internal set; }
/// <summary>
/// The text to apply to the button. If this is not specified <see cref="Emoji"/> becomes required.
/// </summary>
[JsonProperty("label", NullValueHandling = NullValueHandling.Ignore)]
public string Label { get; internal set; }
/// <summary>
/// Whether this button can be pressed.
/// </summary>
[JsonProperty("disabled", NullValueHandling = NullValueHandling.Ignore)]
public bool Disabled { get; internal set; }
/// <summary>
/// The emoji to add to the button. Can be used in conjunction with a label, or as standalone. Must be added if label is not specified.
/// </summary>
[JsonProperty("emoji", NullValueHandling = NullValueHandling.Ignore)]
public DiscordComponentEmoji Emoji { get; internal set; }
/// <summary>
/// Enables this component if it was disabled before.
/// </summary>
/// <returns>The current component.</returns>
public DiscordButtonComponent Enable()
{
this.Disabled = false;
return this;
}
/// <summary>
/// Disables this component.
/// </summary>
/// <returns>The current component.</returns>
public DiscordButtonComponent Disable()
{
this.Disabled = true;
return this;
}
/// <summary>
/// Constructs a new <see cref="DiscordButtonComponent"/>.
/// </summary>
public DiscordButtonComponent()
{
this.Type = ComponentType.Button;
}
/// <summary>
/// Constructs a new button based on another button.
/// </summary>
/// <param name="other">The button to copy.</param>
public DiscordButtonComponent(DiscordButtonComponent other) : this()
{
this.CustomId = other.CustomId;
this.Style = other.Style;
this.Label = other.Label;
this.Disabled = other.Disabled;
this.Emoji = other.Emoji;
}
/// <summary>
/// Constructs a new button with the specified options.
/// </summary>
/// <param name="style">The style/color of the button.</param>
/// <param name="customId">The Id to assign to the button. This is sent back when a user presses it.</param>
/// <param name="label">The text to display on the button, up to 80 characters. Can be left blank if <paramref name="emoji"/>is set.</param>
/// <param name="disabled">Whether this button should be initialized as being disabled. User sees a greyed out button that cannot be interacted with.</param>
/// <param name="emoji">The emoji to add to the button. This is required if <paramref name="label"/> is empty or null.</param>
/// <exception cref="ArgumentException">Is thrown when neither the <paramref name="emoji"/> nor the <paramref name="label"/> is set.</exception>
public DiscordButtonComponent(ButtonStyle style, string customId = null, string label = null, bool disabled = false, DiscordComponentEmoji emoji = null)
{
this.Style = style;
this.CustomId = customId ?? Guid.NewGuid().ToString();
this.Disabled = disabled;
if (emoji != null)
{
this.Label = label;
this.Emoji = emoji;
}
else
{
this.Label = label ?? throw new ArgumentException("Label can only be null if emoji is set.");
this.Emoji = null;
}
this.Type = ComponentType.Button;
}
}
diff --git a/DisCatSharp/Entities/Interaction/Components/Button/DiscordLinkButtonComponent.cs b/DisCatSharp/Entities/Interaction/Components/Button/DiscordLinkButtonComponent.cs
index 3bb70fa34..2d424fae9 100644
--- a/DisCatSharp/Entities/Interaction/Components/Button/DiscordLinkButtonComponent.cs
+++ b/DisCatSharp/Entities/Interaction/Components/Button/DiscordLinkButtonComponent.cs
@@ -1,106 +1,106 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a link button. Clicking a link button does not send an interaction.
/// </summary>
public class DiscordLinkButtonComponent : DiscordComponent
{
/// <summary>
/// The url to open when pressing this button.
/// </summary>
[JsonProperty("url", NullValueHandling = NullValueHandling.Ignore)]
public string Url { get; set; }
/// <summary>
/// The text to add to this button. If this is not specified, <see cref="Emoji"/> must be.
/// </summary>
[JsonProperty("label", NullValueHandling = NullValueHandling.Ignore)]
public string Label { get; set; }
/// <summary>
/// Whether this button can be pressed.
/// </summary>
[JsonProperty("disabled", NullValueHandling = NullValueHandling.Ignore)]
public bool Disabled { get; set; }
/// <summary>
/// The emoji to add to the button. Can be used in conjunction with a label, or as standalone. Must be added if label is not specified.
/// </summary>
[JsonProperty("emoji", NullValueHandling = NullValueHandling.Ignore)]
public DiscordComponentEmoji Emoji { get; set; }
/// <summary>
/// Gets the style.
/// </summary>
[JsonProperty("style", NullValueHandling = NullValueHandling.Ignore)]
internal int Style { get; set; } = 5; // Link = 5; Discord throws 400 otherwise //
/// <summary>
/// Enables this component if it was disabled before.
/// </summary>
/// <returns>The current component.</returns>
public DiscordLinkButtonComponent Enable()
{
this.Disabled = false;
return this;
}
/// <summary>
/// Disables this component.
/// </summary>
/// <returns>The current component.</returns>
public DiscordLinkButtonComponent Disable()
{
this.Disabled = true;
return this;
}
/// <summary>
/// Constructs a new <see cref="DiscordLinkButtonComponent"/>. This type of button does not send back and interaction when pressed.
/// </summary>
/// <param name="url">The url to set the button to.</param>
/// <param name="label">The text to display on the button. Can be left blank if <paramref name="emoji"/> is set.</param>
/// <param name="disabled">Whether or not this button can be pressed.</param>
/// <param name="emoji">The emoji to set with this button. This is required if <paramref name="label"/> is null or empty.</param>
public DiscordLinkButtonComponent(string url, string label, bool disabled = false, DiscordComponentEmoji emoji = null) : this()
{
this.Url = url;
this.Label = label;
this.Disabled = disabled;
this.Emoji = emoji;
}
/// <summary>
/// Initializes a new instance of the <see cref="DiscordLinkButtonComponent"/> class.
/// </summary>
public DiscordLinkButtonComponent()
{
this.Type = ComponentType.Button;
}
}
diff --git a/DisCatSharp/Entities/Interaction/Components/DiscordActionRowComponent.cs b/DisCatSharp/Entities/Interaction/Components/DiscordActionRowComponent.cs
index 65b9b12a5..6fddeb5bb 100644
--- a/DisCatSharp/Entities/Interaction/Components/DiscordActionRowComponent.cs
+++ b/DisCatSharp/Entities/Interaction/Components/DiscordActionRowComponent.cs
@@ -1,67 +1,67 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a row of components. Action rows can have up to five components.
/// </summary>
public sealed class DiscordActionRowComponent : DiscordComponent
{
/// <summary>
/// The components contained within the action row.
/// </summary>
[JsonIgnore]
public IReadOnlyCollection<DiscordComponent> Components
{
get => this._components ?? new List<DiscordComponent>();
set => this._components = new List<DiscordComponent>(value);
}
[JsonProperty("components", NullValueHandling = NullValueHandling.Ignore)]
private List<DiscordComponent> _components;
/// <summary>
/// Constructs a new <see cref="DiscordActionRowComponent"/>.
/// </summary>
/// <param name="components">List of components</param>
public DiscordActionRowComponent(IEnumerable<DiscordComponent> components)
: this()
{
this.Components = components.ToList().AsReadOnly();
}
/// <summary>
/// Initializes a new instance of the <see cref="DiscordActionRowComponent"/> class.
/// </summary>
internal DiscordActionRowComponent()
{
this.Type = ComponentType.ActionRow;
}
}
diff --git a/DisCatSharp/Entities/Interaction/Components/DiscordActionRowComponentResult.cs b/DisCatSharp/Entities/Interaction/Components/DiscordActionRowComponentResult.cs
index 62d538837..b66a9210d 100644
--- a/DisCatSharp/Entities/Interaction/Components/DiscordActionRowComponentResult.cs
+++ b/DisCatSharp/Entities/Interaction/Components/DiscordActionRowComponentResult.cs
@@ -1,53 +1,53 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a <see cref="DiscordActionRowComponentResult"/> resolved from a <see cref="DisCatSharp.Enums.ApplicationCommandType.ModalSubmit"/>.
/// </summary>
public sealed class DiscordActionRowComponentResult
{
/// <summary>
/// The type of component this represents.
/// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
public ComponentType Type { get; internal set; }
/// <summary>
/// The components contained within the resolved action row.
/// </summary>
[JsonProperty("components", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<DiscordComponentResult> Components { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordActionRowComponentResult"/> class.
/// </summary>
internal DiscordActionRowComponentResult()
{ }
}
diff --git a/DisCatSharp/Entities/Interaction/Components/DiscordComponent.cs b/DisCatSharp/Entities/Interaction/Components/DiscordComponent.cs
index 7e3f0e9cf..b28babb91 100644
--- a/DisCatSharp/Entities/Interaction/Components/DiscordComponent.cs
+++ b/DisCatSharp/Entities/Interaction/Components/DiscordComponent.cs
@@ -1,53 +1,53 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.Net.Serialization;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// A component to attach to a message.
/// </summary>
[JsonConverter(typeof(DiscordComponentJsonConverter))]
public class DiscordComponent
{
/// <summary>
/// The type of component this represents.
/// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
public ComponentType Type { get; internal set; }
/// <summary>
/// The Id of this component, if applicable. Not applicable on ActionRow(s) and link buttons.
/// </summary>
[JsonProperty("custom_id", NullValueHandling = NullValueHandling.Ignore)]
public string CustomId { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordComponent"/> class.
/// </summary>
internal DiscordComponent()
{ }
}
diff --git a/DisCatSharp/Entities/Interaction/Components/DiscordComponentResult.cs b/DisCatSharp/Entities/Interaction/Components/DiscordComponentResult.cs
index abeaf85c5..ad4375df9 100644
--- a/DisCatSharp/Entities/Interaction/Components/DiscordComponentResult.cs
+++ b/DisCatSharp/Entities/Interaction/Components/DiscordComponentResult.cs
@@ -1,65 +1,65 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a <see cref="DiscordComponentResult"/> resolved within an <see cref="DiscordActionRowComponentResult"/>.
/// </summary>
public sealed class DiscordComponentResult
{
/// <summary>
/// The type of component this represents.
/// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
public ComponentType Type { get; internal set; }
/// <summary>
/// The Id of this component, if applicable. Not applicable on ActionRow(s) and link buttons.
/// </summary>
[JsonProperty("custom_id", NullValueHandling = NullValueHandling.Ignore)]
public string CustomId { get; internal set; }
/// <summary>
/// The users typed value.
/// </summary>
[JsonProperty("value", NullValueHandling = NullValueHandling.Ignore)]
public string Value { get; internal set; }
/// <summary>
/// The selected values. Only applicable to Selects.
/// </summary>
[JsonProperty("values", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<string> Values { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordComponentResult"/> class.
/// </summary>
internal DiscordComponentResult()
{ }
}
diff --git a/DisCatSharp/Entities/Interaction/Components/DiscordEmojiComponent.cs b/DisCatSharp/Entities/Interaction/Components/DiscordEmojiComponent.cs
index 786115696..4768d528b 100644
--- a/DisCatSharp/Entities/Interaction/Components/DiscordEmojiComponent.cs
+++ b/DisCatSharp/Entities/Interaction/Components/DiscordEmojiComponent.cs
@@ -1,80 +1,80 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Entities;
/// <summary>
/// Represents an emoji to add to a component.
/// </summary>
public sealed class DiscordComponentEmoji
{
/// <summary>
/// The Id of the emoji to use.
/// </summary>
[JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)]
public ulong Id { get; set; }
/// <summary>
/// The name of the emoji to use. Ignored if <see cref="Id"/> is set.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; set; }
/// <summary>
/// Constructs a new component emoji to add to a <see cref="DiscordComponent"/>.
/// </summary>
public DiscordComponentEmoji() { }
/// <summary>
/// Constructs a new component emoji from an emoji Id.
/// </summary>
/// <param name="id">The Id of the emoji to use. Any valid emoji Id can be passed.</param>
public DiscordComponentEmoji(ulong id)
{
this.Id = id;
}
/// <summary>
/// Constructs a new component emoji from unicode.
/// </summary>
/// <param name="name">The unicode emoji to set.</param>
public DiscordComponentEmoji(string name)
{
if (!DiscordEmoji.IsValidUnicode(name))
throw new ArgumentException("Only unicode emojis can be passed.");
this.Name = name;
}
/// <summary>
/// Constructs a new component emoji from an existing <see cref="DiscordEmoji"/>.
/// </summary>
/// <param name="emoji">The emoji to use.</param>
public DiscordComponentEmoji(DiscordEmoji emoji)
{
this.Id = emoji.Id;
this.Name = emoji.Name;
}
}
diff --git a/DisCatSharp/Entities/Interaction/Components/Select/DiscordBaseSelectComponent.cs b/DisCatSharp/Entities/Interaction/Components/Select/DiscordBaseSelectComponent.cs
index 59960d999..0a0bc10f8 100644
--- a/DisCatSharp/Entities/Interaction/Components/Select/DiscordBaseSelectComponent.cs
+++ b/DisCatSharp/Entities/Interaction/Components/Select/DiscordBaseSelectComponent.cs
@@ -1,111 +1,111 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// A select menu with multiple options to choose from.
/// </summary>
public class DiscordBaseSelectComponent : DiscordComponent
{
/// <summary>
/// The text to show when no option is selected.
/// </summary>
[JsonProperty("placeholder", NullValueHandling = NullValueHandling.Ignore)]
public string Placeholder { get; internal set; }
/// <summary>
/// The minimum amount of options that can be selected. Must be less than or equal to <see cref="MaximumSelectedValues"/>. Defaults to one.
/// </summary>
[JsonProperty("min_values", NullValueHandling = NullValueHandling.Ignore)]
public int? MinimumSelectedValues { get; internal set; } = 1;
/// <summary>
/// The maximum amount of options that can be selected. Must be greater than or equal to zero or <see cref="MinimumSelectedValues"/>. Defaults to one.
/// </summary>
[JsonProperty("max_values", NullValueHandling = NullValueHandling.Ignore)]
public int? MaximumSelectedValues { get; internal set; } = 1;
/// <summary>
/// Whether this select can be used.
/// </summary>
[JsonProperty("disabled", NullValueHandling = NullValueHandling.Ignore)]
public bool Disabled { get; internal set; }
/// <summary>
/// Label of component, if used in modal.
/// </summary>
[JsonProperty("label", NullValueHandling = NullValueHandling.Ignore)]
public string Label { get; internal set; } = null;
/// <summary>
/// Constructs a new <see cref="DiscordBaseSelectComponent"/>.
/// </summary>
/// <param name="type">The type of select.</param>
/// <param name="placeholder">Text to show if no option is selected.</param>
/// <param name="customId">The Id to assign to the select component.</param>
/// <param name="minOptions">Minimum count of selectable options.</param>
/// <param name="maxOptions">Maximum count of selectable options.</param>
/// <param name="disabled">Whether this select component should be initialized as being disabled. User sees a greyed out select component that cannot be interacted with.</param>
internal DiscordBaseSelectComponent(ComponentType type, string placeholder, string customId = null, int minOptions = 1, int maxOptions = 1, bool disabled = false)
{
this.Type = type;
this.CustomId = customId ?? Guid.NewGuid().ToString(); ;
this.Disabled = disabled;
this.Placeholder = placeholder;
this.MinimumSelectedValues = minOptions;
this.MaximumSelectedValues = maxOptions;
}
/// <summary>
/// Constructs a new <see cref="DiscordBaseSelectComponent"/> for modals.
/// </summary>
/// <param name="type">The type of select.</param>
/// <param name="label">Maximum count of selectable options.</param>
/// <param name="placeholder">Text to show if no option is selected.</param>
/// <param name="customId">The Id to assign to the select component.</param>
/// <param name="minOptions">Minimum count of selectable options.</param>
/// <param name="maxOptions">Maximum count of selectable options.</param>
/// <param name="disabled">Whether this select component should be initialized as being disabled. User sees a greyed out select component that cannot be interacted with.</param>
internal DiscordBaseSelectComponent(ComponentType type, string label, string placeholder, string customId = null, int minOptions = 1, int maxOptions = 1, bool disabled = false)
{
this.Type = type;
this.Label = label;
this.CustomId = customId ?? Guid.NewGuid().ToString(); ;
this.Disabled = disabled;
this.Placeholder = placeholder;
this.MinimumSelectedValues = minOptions;
this.MaximumSelectedValues = maxOptions;
}
internal DiscordBaseSelectComponent()
{
}
}
diff --git a/DisCatSharp/Entities/Interaction/Components/Select/DiscordChannelSelectComponent.cs b/DisCatSharp/Entities/Interaction/Components/Select/DiscordChannelSelectComponent.cs
index 5165e0468..f769d73bb 100644
--- a/DisCatSharp/Entities/Interaction/Components/Select/DiscordChannelSelectComponent.cs
+++ b/DisCatSharp/Entities/Interaction/Components/Select/DiscordChannelSelectComponent.cs
@@ -1,104 +1,104 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// A select menu with multiple options to choose from.
/// </summary>
public sealed class DiscordChannelSelectComponent : DiscordBaseSelectComponent
{
/// <summary>
/// The channel types to filter by.
/// </summary>
[JsonProperty("channel_types", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<ChannelType> ChannelTypes { get; internal set; } = null;
/// <summary>
/// Enables this component if it was disabled before.
/// </summary>
/// <returns>The current component.</returns>
public DiscordChannelSelectComponent Enable()
{
this.Disabled = false;
return this;
}
/// <summary>
/// Disables this component.
/// </summary>
/// <returns>The current component.</returns>
public DiscordChannelSelectComponent Disable()
{
this.Disabled = true;
return this;
}
// TODO: Can we set required
/// <summary>
/// Constructs a new <see cref="DiscordChannelSelectComponent"/>.
/// </summary>
/// <param name="placeholder">Text to show if no option is selected.</param>
/// <param name="channelTypes">The channel types to filter by.</param>
/// <param name="customId">The Id to assign to the select component.</param>
/// <param name="minOptions">Minimum count of selectable options.</param>
/// <param name="maxOptions">Maximum count of selectable options.</param>
/// <param name="disabled">Whether this select component should be initialized as being disabled. User sees a greyed out select component that cannot be interacted with.</param>
public DiscordChannelSelectComponent(string placeholder, IEnumerable<ChannelType> channelTypes = null, string customId = null, int minOptions = 1, int maxOptions = 1, bool disabled = false)
: base(ComponentType.ChannelSelect, placeholder, customId, minOptions, maxOptions, disabled)
{
this.ChannelTypes = channelTypes?.ToArray() ?? Array.Empty<ChannelType>();
}
/// <summary>
/// Constructs a new <see cref="DiscordChannelSelectComponent"/> for modals.
/// </summary>
/// <param name="label">Maximum count of selectable options.</param>
/// <param name="placeholder">Text to show if no option is selected.</param>
/// <param name="channelTypes">The channel types to filter by.</param>
/// <param name="customId">The Id to assign to the select component.</param>
/// <param name="minOptions">Minimum count of selectable options.</param>
/// <param name="maxOptions">Maximum count of selectable options.</param>
/// <param name="disabled">Whether this select component should be initialized as being disabled. User sees a greyed out select component that cannot be interacted with.</param>
public DiscordChannelSelectComponent(string label, string placeholder, IEnumerable<ChannelType> channelTypes = null, string customId = null, int minOptions = 1, int maxOptions = 1, bool disabled = false)
: base(ComponentType.ChannelSelect, label, placeholder, customId, minOptions, maxOptions, disabled)
{
this.ChannelTypes = channelTypes?.ToArray() ?? Array.Empty<ChannelType>();
}
/// <summary>
/// Constructs a new <see cref="DiscordChannelSelectComponent"/>.
/// </summary>
public DiscordChannelSelectComponent() : base()
{
this.Type = ComponentType.ChannelSelect;
}
}
diff --git a/DisCatSharp/Entities/Interaction/Components/Select/DiscordMentionableSelectComponent.cs b/DisCatSharp/Entities/Interaction/Components/Select/DiscordMentionableSelectComponent.cs
index e8ea5cb44..5749992a8 100644
--- a/DisCatSharp/Entities/Interaction/Components/Select/DiscordMentionableSelectComponent.cs
+++ b/DisCatSharp/Entities/Interaction/Components/Select/DiscordMentionableSelectComponent.cs
@@ -1,86 +1,86 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
namespace DisCatSharp.Entities;
/// <summary>
/// A select menu with multiple options to choose from.
/// </summary>
public sealed class DiscordMentionableSelectComponent : DiscordBaseSelectComponent
{
/// <summary>
/// Enables this component if it was disabled before.
/// </summary>
/// <returns>The current component.</returns>
public DiscordMentionableSelectComponent Enable()
{
this.Disabled = false;
return this;
}
/// <summary>
/// Disables this component.
/// </summary>
/// <returns>The current component.</returns>
public DiscordMentionableSelectComponent Disable()
{
this.Disabled = true;
return this;
}
// TODO: Can we set required
/// <summary>
/// Constructs a new <see cref="DiscordMentionableSelectComponent"/>.
/// </summary>
/// <param name="placeholder">Text to show if no option is selected.</param>
/// <param name="customId">The Id to assign to the select component.</param>
/// <param name="minOptions">Minimum count of selectable options.</param>
/// <param name="maxOptions">Maximum count of selectable options.</param>
/// <param name="disabled">Whether this select component should be initialized as being disabled. User sees a greyed out select component that cannot be interacted with.</param>
public DiscordMentionableSelectComponent(string placeholder, string customId = null, int minOptions = 1, int maxOptions = 1, bool disabled = false)
: base(ComponentType.MentionableSelect, placeholder, customId, minOptions, maxOptions, disabled)
{ }
/// <summary>
/// Constructs a new <see cref="DiscordMentionableSelectComponent"/> for modals.
/// </summary>
/// <param name="label">Maximum count of selectable options.</param>
/// <param name="placeholder">Text to show if no option is selected.</param>
/// <param name="customId">The Id to assign to the select component.</param>
/// <param name="minOptions">Minimum count of selectable options.</param>
/// <param name="maxOptions">Maximum count of selectable options.</param>
/// <param name="disabled">Whether this select component should be initialized as being disabled. User sees a greyed out select component that cannot be interacted with.</param>
public DiscordMentionableSelectComponent(string label, string placeholder, string customId = null, int minOptions = 1, int maxOptions = 1, bool disabled = false)
: base(ComponentType.MentionableSelect, label, placeholder, customId, minOptions, maxOptions, disabled)
{ }
/// <summary>
/// Constructs a new <see cref="DiscordMentionableSelectComponent"/>.
/// </summary>
public DiscordMentionableSelectComponent() : base()
{
this.Type = ComponentType.MentionableSelect;
}
}
diff --git a/DisCatSharp/Entities/Interaction/Components/Select/DiscordRoleSelectComponent.cs b/DisCatSharp/Entities/Interaction/Components/Select/DiscordRoleSelectComponent.cs
index dd351f918..54b91692d 100644
--- a/DisCatSharp/Entities/Interaction/Components/Select/DiscordRoleSelectComponent.cs
+++ b/DisCatSharp/Entities/Interaction/Components/Select/DiscordRoleSelectComponent.cs
@@ -1,86 +1,86 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
namespace DisCatSharp.Entities;
/// <summary>
/// A select menu with multiple options to choose from.
/// </summary>
public sealed class DiscordRoleSelectComponent : DiscordBaseSelectComponent
{
/// <summary>
/// Enables this component if it was disabled before.
/// </summary>
/// <returns>The current component.</returns>
public DiscordRoleSelectComponent Enable()
{
this.Disabled = false;
return this;
}
/// <summary>
/// Disables this component.
/// </summary>
/// <returns>The current component.</returns>
public DiscordRoleSelectComponent Disable()
{
this.Disabled = true;
return this;
}
// TODO: Can we set required
/// <summary>
/// Constructs a new <see cref="DiscordRoleSelectComponent"/>.
/// </summary>
/// <param name="placeholder">Text to show if no option is selected.</param>
/// <param name="customId">The Id to assign to the select component.</param>
/// <param name="minOptions">Minimum count of selectable options.</param>
/// <param name="maxOptions">Maximum count of selectable options.</param>
/// <param name="disabled">Whether this select component should be initialized as being disabled. User sees a greyed out select component that cannot be interacted with.</param>
public DiscordRoleSelectComponent(string placeholder, string customId = null, int minOptions = 1, int maxOptions = 1, bool disabled = false)
: base(ComponentType.RoleSelect, placeholder, customId, minOptions, maxOptions, disabled)
{ }
/// <summary>
/// Constructs a new <see cref="DiscordRoleSelectComponent"/> for modals.
/// </summary>
/// <param name="label">Maximum count of selectable options.</param>
/// <param name="placeholder">Text to show if no option is selected.</param>
/// <param name="customId">The Id to assign to the select component.</param>
/// <param name="minOptions">Minimum count of selectable options.</param>
/// <param name="maxOptions">Maximum count of selectable options.</param>
/// <param name="disabled">Whether this select component should be initialized as being disabled. User sees a greyed out select component that cannot be interacted with.</param>
public DiscordRoleSelectComponent(string label, string placeholder, string customId = null, int minOptions = 1, int maxOptions = 1, bool disabled = false)
: base(ComponentType.RoleSelect, label, placeholder, customId, minOptions, maxOptions, disabled)
{ }
/// <summary>
/// Constructs a new <see cref="DiscordRoleSelectComponent"/>.
/// </summary>
public DiscordRoleSelectComponent() : base()
{
this.Type = ComponentType.RoleSelect;
}
}
diff --git a/DisCatSharp/Entities/Interaction/Components/Select/DiscordStringSelectComponent.cs b/DisCatSharp/Entities/Interaction/Components/Select/DiscordStringSelectComponent.cs
index 0cfd579d1..200de909a 100644
--- a/DisCatSharp/Entities/Interaction/Components/Select/DiscordStringSelectComponent.cs
+++ b/DisCatSharp/Entities/Interaction/Components/Select/DiscordStringSelectComponent.cs
@@ -1,102 +1,102 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// A select menu with multiple options to choose from.
/// </summary>
public sealed class DiscordStringSelectComponent : DiscordBaseSelectComponent
{
/// <summary>
/// The options to pick from on this component.
/// </summary>
[JsonProperty("options", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<DiscordStringSelectComponentOption> Options { get; internal set; } = Array.Empty<DiscordStringSelectComponentOption>();
/// <summary>
/// Enables this component if it was disabled before.
/// </summary>
/// <returns>The current component.</returns>
public DiscordStringSelectComponent Enable()
{
this.Disabled = false;
return this;
}
/// <summary>
/// Disables this component.
/// </summary>
/// <returns>The current component.</returns>
public DiscordStringSelectComponent Disable()
{
this.Disabled = true;
return this;
}
/// <summary>
/// Constructs a new <see cref="DiscordStringSelectComponent"/>.
/// </summary>
/// <param name="placeholder">Text to show if no option is selected.</param>
/// <param name="options">Array of options</param>
/// <param name="customId">The Id to assign to the select component.</param>
/// <param name="minOptions">Minimum count of selectable options.</param>
/// <param name="maxOptions">Maximum count of selectable options.</param>
/// <param name="disabled">Whether this select component should be initialized as being disabled. User sees a greyed out select component that cannot be interacted with.</param>
public DiscordStringSelectComponent(string placeholder, IEnumerable<DiscordStringSelectComponentOption> options, string customId = null, int minOptions = 1, int maxOptions = 1, bool disabled = false)
: base(ComponentType.StringSelect, placeholder, customId, minOptions, maxOptions, disabled)
{
this.Options = options.ToArray();
}
/// <summary>
/// Constructs a new <see cref="DiscordStringSelectComponent"/> for modals.
/// </summary>
/// <param name="label">Maximum count of selectable options.</param>
/// <param name="placeholder">Text to show if no option is selected.</param>
/// <param name="options">Array of options</param>
/// <param name="customId">The Id to assign to the select component.</param>
/// <param name="minOptions">Minimum count of selectable options.</param>
/// <param name="maxOptions">Maximum count of selectable options.</param>
/// <param name="disabled">Whether this select component should be initialized as being disabled. User sees a greyed out select component that cannot be interacted with.</param>
public DiscordStringSelectComponent(string label, string placeholder, IEnumerable<DiscordStringSelectComponentOption> options, string customId = null, int minOptions = 1, int maxOptions = 1, bool disabled = false)
: base(ComponentType.StringSelect, label, placeholder, customId, minOptions, maxOptions, disabled)
{
this.Options = options.ToArray();
}
/// <summary>
/// Constructs a new <see cref="DiscordStringSelectComponent"/>.
/// </summary>
public DiscordStringSelectComponent() : base()
{
this.Type = ComponentType.StringSelect;
}
}
diff --git a/DisCatSharp/Entities/Interaction/Components/Select/DiscordStringSelectComponentOption.cs b/DisCatSharp/Entities/Interaction/Components/Select/DiscordStringSelectComponentOption.cs
index cfe4bd36b..f18d1479b 100644
--- a/DisCatSharp/Entities/Interaction/Components/Select/DiscordStringSelectComponentOption.cs
+++ b/DisCatSharp/Entities/Interaction/Components/Select/DiscordStringSelectComponentOption.cs
@@ -1,87 +1,87 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Entities;
/// <summary>
/// Represents options for <see cref="DiscordBaseSelectComponent"/>.
/// </summary>
public sealed class DiscordStringSelectComponentOption
{
/// <summary>
/// The label to add. This is required.
/// </summary>
[JsonProperty("label", NullValueHandling = NullValueHandling.Ignore)]
public string Label { get; internal set; }
/// <summary>
/// The value of this option. Akin to the Custom Id of components.
/// </summary>
[JsonProperty("value", NullValueHandling = NullValueHandling.Ignore)]
public string Value { get; internal set; }
/// <summary>
/// Whether this option is default. If true, this option will be pre-selected. Defaults to false.
/// </summary>
[JsonProperty("default", NullValueHandling = NullValueHandling.Ignore)]
public bool Default { get; internal set; } // false //
/// <summary>
/// The description of this option. This is optional.
/// </summary>
[JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)]
public string Description { get; internal set; }
/// <summary>
/// The emoji of this option. This is optional.
/// </summary>
[JsonProperty("emoji", NullValueHandling = NullValueHandling.Ignore)]
public DiscordComponentEmoji Emoji { get; internal set; }
/// <summary>
/// Constructs a new <see cref="DiscordStringSelectComponentOption"/>.
/// </summary>
/// <param name="label">The label of this option.</param>
/// <param name="value">The value of this option.</param>
/// <param name="description">Description of the option.</param>
/// <param name="isDefault">Whether this option is default. If true, this option will be pre-selected.</param>
/// <param name="emoji">The emoji to set with this option.</param>
public DiscordStringSelectComponentOption(string label, string value, string description = null, bool isDefault = false, DiscordComponentEmoji emoji = null)
{
if (label.Length > 100)
throw new NotSupportedException("Select label can't be longer then 100 chars.");
if (value.Length > 100)
throw new NotSupportedException("Select value can't be longer then 100 chars.");
if (description != null && description.Length > 100)
throw new NotSupportedException("Select description can't be longer then 100 chars.");
this.Label = label;
this.Value = value;
this.Description = description;
this.Default = isDefault;
this.Emoji = emoji;
}
}
diff --git a/DisCatSharp/Entities/Interaction/Components/Select/DiscordUserSelectComponent.cs b/DisCatSharp/Entities/Interaction/Components/Select/DiscordUserSelectComponent.cs
index 5ed33c4d6..c46d8273e 100644
--- a/DisCatSharp/Entities/Interaction/Components/Select/DiscordUserSelectComponent.cs
+++ b/DisCatSharp/Entities/Interaction/Components/Select/DiscordUserSelectComponent.cs
@@ -1,84 +1,84 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
namespace DisCatSharp.Entities;
/// <summary>
/// A select menu with multiple options to choose from.
/// </summary>
public sealed class DiscordUserSelectComponent : DiscordBaseSelectComponent
{
/// <summary>
/// Enables this component if it was disabled before.
/// </summary>
/// <returns>The current component.</returns>
public DiscordUserSelectComponent Enable()
{
this.Disabled = false;
return this;
}
/// <summary>
/// Disables this component.
/// </summary>
/// <returns>The current component.</returns>
public DiscordUserSelectComponent Disable()
{
this.Disabled = true;
return this;
}
/// <summary>
/// Constructs a new <see cref="DiscordUserSelectComponent"/>.
/// </summary>
/// <param name="placeholder">Text to show if no option is selected.</param>
/// <param name="customId">The Id to assign to the select component.</param>
/// <param name="minOptions">Minimum count of selectable options.</param>
/// <param name="maxOptions">Maximum count of selectable options.</param>
/// <param name="disabled">Whether this select component should be initialized as being disabled. User sees a greyed out select component that cannot be interacted with.</param>
public DiscordUserSelectComponent(string placeholder, string customId = null, int minOptions = 1, int maxOptions = 1, bool disabled = false)
: base(ComponentType.UserSelect, placeholder, customId, minOptions, maxOptions, disabled)
{ }
/// <summary>
/// Constructs a new <see cref="DiscordUserSelectComponent"/> for modals.
/// </summary>
/// <param name="label">Maximum count of selectable options.</param>
/// <param name="placeholder">Text to show if no option is selected.</param>
/// <param name="customId">The Id to assign to the select component.</param>
/// <param name="minOptions">Minimum count of selectable options.</param>
/// <param name="maxOptions">Maximum count of selectable options.</param>
/// <param name="disabled">Whether this select component should be initialized as being disabled. User sees a greyed out select component that cannot be interacted with.</param>
public DiscordUserSelectComponent(string label, string placeholder, string customId = null, int minOptions = 1, int maxOptions = 1, bool disabled = false)
: base(ComponentType.UserSelect, label, placeholder, customId, minOptions, maxOptions, disabled)
{ }
/// <summary>
/// Constructs a new <see cref="DiscordUserSelectComponent"/>.
/// </summary>
public DiscordUserSelectComponent() : base()
{
this.Type = ComponentType.UserSelect;
}
}
diff --git a/DisCatSharp/Entities/Interaction/Components/Text/DiscordTextComponent.cs b/DisCatSharp/Entities/Interaction/Components/Text/DiscordTextComponent.cs
index 76c2261b2..9c6de4d67 100644
--- a/DisCatSharp/Entities/Interaction/Components/Text/DiscordTextComponent.cs
+++ b/DisCatSharp/Entities/Interaction/Components/Text/DiscordTextComponent.cs
@@ -1,157 +1,157 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a text component that can be submitted. Fires <see cref="DisCatSharp.DiscordClient.ComponentInteractionCreated"/> event when submitted.
/// </summary>
public sealed class DiscordTextComponent : DiscordComponent
{
/// <summary>
/// The style of the text component.
/// </summary>
[JsonProperty("style", NullValueHandling = NullValueHandling.Ignore)]
public TextComponentStyle Style { get; internal set; }
/// <summary>
/// The text description to apply to the text component.
/// </summary>
[JsonProperty("label", NullValueHandling = NullValueHandling.Ignore)]
public string Label { get; internal set; }
/// <summary>
/// The placeholder for the text component.
/// </summary>
[JsonProperty("placeholder", NullValueHandling = NullValueHandling.Ignore)]
public string Placeholder { get; internal set; }
/// <summary>
/// The pre-filled value for the text component.
/// </summary>
[JsonProperty("value", NullValueHandling = NullValueHandling.Ignore)]
public string Value { get; internal set; }
/// <summary>
/// The minimal length of text input.
/// Defaults to 0.
/// </summary>
[JsonProperty("min_length", NullValueHandling = NullValueHandling.Ignore)]
public int? MinLength { get; internal set; } = 0;
/// <summary>
/// The maximal length of text input.
/// </summary>
[JsonProperty("max_length", NullValueHandling = NullValueHandling.Ignore)]
public int? MaxLength { get; internal set; }
// NOTE: Probably will be introduced in future
/*/// <summary>
/// Whether this text component can be used.
/// </summary>
[JsonProperty("disabled", NullValueHandling = NullValueHandling.Ignore)]
public bool Disabled { get; internal set; }*/
/// <summary>
/// Whether this text component is required.
/// Defaults to true.
/// </summary>
[JsonProperty("required", NullValueHandling = NullValueHandling.Ignore)]
public bool Required { get; internal set; }
/*/// <summary>
/// Enables this component if it was disabled before.
/// </summary>
/// <returns>The current component.</returns>
public DiscordTextComponent Enable()
{
this.Disabled = false;
return this;
}
/// <summary>
/// Disables this component.
/// </summary>
/// <returns>The current component.</returns>
public DiscordTextComponent Disable()
{
this.Disabled = true;
return this;
}*/
/// <summary>
/// Constructs a new <see cref="DiscordTextComponent"/>.
/// </summary>
internal DiscordTextComponent()
{
this.Type = ComponentType.InputText;
}
/// <summary>
/// Constructs a new text component based on another text component.
/// </summary>
/// <param name="other">The button to copy.</param>
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;
}
/// <summary>
/// Constructs a new text component field with the specified options.
/// </summary>
/// <param name="style">The style of the text component.</param>
/// <param name="customId">The Id to assign to the text component. This is sent back when a user presses it.</param>
/// <param name="label">The text to display before the text component, up to 80 characters. Required, but set to null to avoid breaking change.</param>
/// <param name="placeholder">The placeholder for the text input.</param>
/// <param name="minLength">The minimal length of text input.</param>
/// <param name="maxLength">The maximal length of text input.</param>
/// <param name="required">Whether this text component should be required.</param>
/// <param name="defaultValue">Pre-filled value for text field.</param>
/// <exception cref="ArgumentException">Is thrown when no label is set.</exception>
public DiscordTextComponent(TextComponentStyle style, string customId = null, string label = null, string placeholder = null, int? minLength = null, int? maxLength = null, bool required = true, string defaultValue = null)
{
this.Style = style;
this.Label = label ?? throw new ArgumentException("A label is required.");
this.CustomId = customId ?? Guid.NewGuid().ToString();
this.MinLength = minLength;
this.MaxLength = style == TextComponentStyle.Small ? 256 : maxLength;
this.Placeholder = placeholder;
//this.Disabled = disabled;
this.Required = required;
this.Value = defaultValue;
this.Type = ComponentType.InputText;
}
}
diff --git a/DisCatSharp/Entities/Interaction/DiscordFollowupMessageBuilder.cs b/DisCatSharp/Entities/Interaction/DiscordFollowupMessageBuilder.cs
index 925b7e2fc..98a779ac3 100644
--- a/DisCatSharp/Entities/Interaction/DiscordFollowupMessageBuilder.cs
+++ b/DisCatSharp/Entities/Interaction/DiscordFollowupMessageBuilder.cs
@@ -1,313 +1,313 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Constructs a followup message to an interaction.
/// </summary>
public sealed class DiscordFollowupMessageBuilder
{
/// <summary>
/// Whether this followup message is text-to-speech.
/// </summary>
public bool IsTts { get; set; }
/// <summary>
/// Whether this followup message should be ephemeral.
/// </summary>
public bool IsEphemeral { get; set; }
/// <summary>
/// Indicates this message is ephemeral.
/// </summary>
internal int? Flags
=> this.IsEphemeral ? 64 : null;
/// <summary>
/// Message to send on followup message.
/// </summary>
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;
/// <summary>
/// Embeds to send on followup message.
/// </summary>
public IReadOnlyList<DiscordEmbed> Embeds => this._embeds;
private readonly List<DiscordEmbed> _embeds = new();
/// <summary>
/// Files to send on this followup message.
/// </summary>
public IReadOnlyList<DiscordMessageFile> Files => this._files;
private readonly List<DiscordMessageFile> _files = new();
/// <summary>
/// Components to send on this followup message.
/// </summary>
public IReadOnlyList<DiscordActionRowComponent> Components => this._components;
private readonly List<DiscordActionRowComponent> _components = new();
/// <summary>
/// Mentions to send on this followup message.
/// </summary>
public IReadOnlyList<IMention> Mentions => this._mentions;
private readonly List<IMention> _mentions = new();
/// <summary>
/// Appends a collection of components to the message.
/// </summary>
/// <param name="components">The collection of components to add.</param>
/// <returns>The builder to chain calls with.</returns>
/// <exception cref="ArgumentException"><paramref name="components"/> contained more than 5 components.</exception>
public DiscordFollowupMessageBuilder AddComponents(params DiscordComponent[] components)
=> this.AddComponents((IEnumerable<DiscordComponent>)components);
/// <summary>
/// Appends several rows of components to the message
/// </summary>
/// <param name="components">The rows of components to add, holding up to five each.</param>
/// <returns></returns>
public DiscordFollowupMessageBuilder AddComponents(IEnumerable<DiscordActionRowComponent> 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;
}
/// <summary>
/// Appends a collection of components to the message.
/// </summary>
/// <param name="components">The collection of components to add.</param>
/// <returns>The builder to chain calls with.</returns>
/// <exception cref="ArgumentException"><paramref name="components"/> contained more than 5 components.</exception>
public DiscordFollowupMessageBuilder AddComponents(IEnumerable<DiscordComponent> 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;
}
/// <summary>
/// Indicates if the followup message must use text-to-speech.
/// </summary>
/// <param name="tts">Text-to-speech</param>
/// <returns>The builder to chain calls with.</returns>
public DiscordFollowupMessageBuilder WithTts(bool tts)
{
this.IsTts = tts;
return this;
}
/// <summary>
/// Sets the message to send with the followup message..
/// </summary>
/// <param name="content">Message to send.</param>
/// <returns>The builder to chain calls with.</returns>
public DiscordFollowupMessageBuilder WithContent(string content)
{
this.Content = content;
return this;
}
/// <summary>
/// Adds an embed to the followup message.
/// </summary>
/// <param name="embed">Embed to add.</param>
/// <returns>The builder to chain calls with.</returns>
public DiscordFollowupMessageBuilder AddEmbed(DiscordEmbed embed)
{
this._embeds.Add(embed);
return this;
}
/// <summary>
/// Adds the given embeds to the followup message.
/// </summary>
/// <param name="embeds">Embeds to add.</param>
/// <returns>The builder to chain calls with.</returns>
public DiscordFollowupMessageBuilder AddEmbeds(IEnumerable<DiscordEmbed> embeds)
{
this._embeds.AddRange(embeds);
return this;
}
/// <summary>
/// Adds a file to the followup message.
/// </summary>
/// <param name="filename">Name of the file.</param>
/// <param name="data">File data.</param>
/// <param name="resetStreamPosition">Tells the API Client to reset the stream position to what it was after the file is sent.</param>
/// <param name="description">Description of the file.</param>
/// <returns>The builder to chain calls with.</returns>
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;
}
/// <summary>
/// Sets if the message has files to be sent.
/// </summary>
/// <param name="stream">The Stream to the file.</param>
/// <param name="resetStreamPosition">Tells the API Client to reset the stream position to what it was after the file is sent.</param>
/// <param name="description">Description of the file.</param>
/// <returns>The builder to chain calls with.</returns>
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;
}
/// <summary>
/// Adds the given files to the followup message.
/// </summary>
/// <param name="files">Dictionary of file name and file data.</param>
/// <param name="resetStreamPosition">Tells the API Client to reset the stream position to what it was after the file is sent.</param>
/// <returns>The builder to chain calls with.</returns>
public DiscordFollowupMessageBuilder AddFiles(Dictionary<string, Stream> 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;
}
/// <summary>
/// Adds the mention to the mentions to parse, etc. with the followup message.
/// </summary>
/// <param name="mention">Mention to add.</param>
/// <returns>The builder to chain calls with.</returns>
public DiscordFollowupMessageBuilder AddMention(IMention mention)
{
this._mentions.Add(mention);
return this;
}
/// <summary>
/// Adds the mentions to the mentions to parse, etc. with the followup message.
/// </summary>
/// <param name="mentions">Mentions to add.</param>
/// <returns>The builder to chain calls with.</returns>
public DiscordFollowupMessageBuilder AddMentions(IEnumerable<IMention> mentions)
{
this._mentions.AddRange(mentions);
return this;
}
/// <summary>
/// Sets the followup message to be ephemeral.
/// </summary>
/// <param name="ephemeral">Whether the followup should be ephemeral. Defaults to true.</param>
public DiscordFollowupMessageBuilder AsEphemeral(bool ephemeral = true)
{
this.IsEphemeral = ephemeral;
return this;
}
/// <summary>
/// Clears all message components on this builder.
/// </summary>
public void ClearComponents()
=> this._components.Clear();
/// <summary>
/// Allows for clearing the Followup Message builder so that it can be used again to send a new message.
/// </summary>
public void Clear()
{
this.Content = "";
this._embeds.Clear();
this.IsTts = false;
this._mentions.Clear();
this._files.Clear();
this.IsEphemeral = false;
this._components.Clear();
}
/// <summary>
/// Validates the builder.
/// </summary>
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 9d445e714..d18d247d7 100644
--- a/DisCatSharp/Entities/Interaction/DiscordInteraction.cs
+++ b/DisCatSharp/Entities/Interaction/DiscordInteraction.cs
@@ -1,227 +1,227 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Threading.Tasks;
using DisCatSharp.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents an interaction that was invoked.
/// </summary>
public sealed class DiscordInteraction : SnowflakeObject
{
/// <summary>
/// Gets the type of interaction invoked.
/// </summary>
[JsonProperty("type")]
public InteractionType Type { get; internal set; }
/// <summary>
/// Gets the command data for this interaction.
/// </summary>
[JsonProperty("data", NullValueHandling = NullValueHandling.Ignore)]
public DiscordInteractionData Data { get; internal set; }
/// <summary>
/// Gets the Id of the guild that invoked this interaction.
/// </summary>
[JsonIgnore]
public ulong? GuildId { get; internal set; }
/// <summary>
/// Gets the guild that invoked this interaction.
/// </summary>
[JsonIgnore]
public DiscordGuild Guild
=> (this.Discord as DiscordClient).InternalGetCachedGuild(this.GuildId);
/// <summary>
/// Gets the Id of the channel that invoked this interaction.
/// </summary>
[JsonIgnore]
public ulong ChannelId { get; internal set; }
/// <summary>
/// Gets the channel that invoked this interaction.
/// </summary>
[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 } : new DiscordChannel() { Id = this.ChannelId, Discord = this.Discord });
/// <summary>
/// Gets the user that invoked this interaction.
/// <para>This can be cast to a <see cref="DisCatSharp.Entities.DiscordMember"/> if created in a guild.</para>
/// </summary>
[JsonIgnore]
public DiscordUser User { get; internal set; }
/// <summary>
/// Gets the continuation token for responding to this interaction.
/// </summary>
[JsonProperty("token")]
public string Token { get; internal set; }
/// <summary>
/// Gets the version number for this interaction type.
/// </summary>
[JsonProperty("version")]
public int Version { get; internal set; }
/// <summary>
/// Gets the ID of the application that created this interaction.
/// </summary>
[JsonProperty("application_id")]
public ulong ApplicationId { get; internal set; }
/// <summary>
/// The message this interaction was created with, if any.
/// </summary>
[JsonProperty("message")]
internal DiscordMessage Message { get; set; }
/// <summary>
/// Gets the invoking user locale.
/// </summary>
[JsonProperty("locale", NullValueHandling = NullValueHandling.Ignore)]
public string Locale { get; internal set; }
/// <summary>
/// Gets the guild locale if applicable.
/// </summary>
[JsonProperty("guild_locale", NullValueHandling = NullValueHandling.Ignore)]
public string GuildLocale { get; internal set; }
/// <summary>
/// Gets the applications permissions.
/// </summary>
[JsonProperty("app_permissions", NullValueHandling = NullValueHandling.Ignore)]
public Permissions AppPermissions { get; internal set; }
/// <summary>
/// Creates a response to this interaction.
/// </summary>
/// <param name="type">The type of the response.</param>
/// <param name="builder">The data, if any, to send.</param>
public Task CreateResponseAsync(InteractionResponseType type, DiscordInteractionResponseBuilder builder = null)
=> this.Discord.ApiClient.CreateInteractionResponseAsync(this.Id, this.Token, type, builder);
/// <summary>
/// Creates a modal response to this interaction.
/// </summary>
/// <param name="builder">The data to send.</param>
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.");
/// <summary>
/// Gets the original interaction response.
/// </summary>
/// <returns>The original message that was sent. This <b>does not work on ephemeral messages.</b></returns>
public Task<DiscordMessage> GetOriginalResponseAsync()
=> this.Discord.ApiClient.GetOriginalInteractionResponseAsync(this.Discord.CurrentApplication.Id, this.Token);
/// <summary>
/// Edits the original interaction response.
/// </summary>
/// <param name="builder">The webhook builder.</param>
/// <returns>The edited <see cref="DiscordMessage"/>.</returns>
public async Task<DiscordMessage> EditOriginalResponseAsync(DiscordWebhookBuilder builder)
{
builder.Validate(isInteractionResponse: true);
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.AttachmentsInternal.AddRange(attachments);
}
}
else if (builder.KeepAttachmentsInternal.HasValue)
{
builder.AttachmentsInternal.Clear();
}
return await this.Discord.ApiClient.EditOriginalInteractionResponseAsync(this.Discord.CurrentApplication.Id, this.Token, builder).ConfigureAwait(false);
}
/// <summary>
/// Deletes the original interaction response.
/// </summary>>
public Task DeleteOriginalResponseAsync()
=> this.Discord.ApiClient.DeleteOriginalInteractionResponseAsync(this.Discord.CurrentApplication.Id, this.Token);
/// <summary>
/// Creates a follow up message to this interaction.
/// </summary>
/// <param name="builder">The webhook builder.</param>
/// <returns>The created <see cref="DiscordMessage"/>.</returns>
public async Task<DiscordMessage> CreateFollowupMessageAsync(DiscordFollowupMessageBuilder builder)
{
builder.Validate();
return await this.Discord.ApiClient.CreateFollowupMessageAsync(this.Discord.CurrentApplication.Id, this.Token, builder).ConfigureAwait(false);
}
/// <summary>
/// Gets a follow up message.
/// </summary>
/// <param name="messageId">The id of the follow up message.</param>
public Task<DiscordMessage> GetFollowupMessageAsync(ulong messageId)
=> this.Discord.ApiClient.GetFollowupMessageAsync(this.Discord.CurrentApplication.Id, this.Token, messageId);
/// <summary>
/// Edits a follow up message.
/// </summary>
/// <param name="messageId">The id of the follow up message.</param>
/// <param name="builder">The webhook builder.</param>
/// <returns>The edited <see cref="DiscordMessage"/>.</returns>
public async Task<DiscordMessage> EditFollowupMessageAsync(ulong messageId, DiscordWebhookBuilder builder)
{
builder.Validate(isFollowup: true);
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.AttachmentsInternal.AddRange(attachments);
}
}
else if (builder.KeepAttachmentsInternal.HasValue)
{
builder.AttachmentsInternal.Clear();
}
return await this.Discord.ApiClient.EditFollowupMessageAsync(this.Discord.CurrentApplication.Id, this.Token, messageId, builder).ConfigureAwait(false);
}
/// <summary>
/// Deletes a follow up message.
/// </summary>
/// <param name="messageId">The id of the follow up message.</param>
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 cdd888acd..f6cf0bc49 100644
--- a/DisCatSharp/Entities/Interaction/DiscordInteractionApplicationCommandCallbackData.cs
+++ b/DisCatSharp/Entities/Interaction/DiscordInteractionApplicationCommandCallbackData.cs
@@ -1,107 +1,107 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a interactions application command callback data.
/// </summary>
internal class DiscordInteractionApplicationCommandCallbackData
{
/// <summary>
/// Whether this message is text to speech.
/// </summary>
[JsonProperty("tts", NullValueHandling = NullValueHandling.Ignore)]
public bool? IsTts { get; internal set; }
/// <summary>
/// Gets the content.
/// </summary>
[JsonProperty("content", NullValueHandling = NullValueHandling.Ignore)]
public string Content { get; internal set; }
/// <summary>
/// Gets the embeds.
/// </summary>
[JsonProperty("embeds", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<DiscordEmbed> Embeds { get; internal set; }
/// <summary>
/// Gets the mentions.
/// </summary>
[JsonProperty("allowed_mentions", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<IMention> Mentions { get; internal set; }
/// <summary>
/// Gets the flags.
/// </summary>
[JsonProperty("flags", NullValueHandling = NullValueHandling.Ignore)]
public MessageFlags? Flags { get; internal set; }
/// <summary>
/// Gets the components.
/// </summary>
[JsonProperty("components", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyCollection<DiscordActionRowComponent> Components { get; internal set; }
/// <summary>
/// Gets the autocomplete choices.
/// </summary>
[JsonProperty("choices", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyCollection<DiscordApplicationCommandAutocompleteChoice> Choices { get; internal set; }
/// <summary>
/// Gets the attachments.
/// </summary>
[JsonProperty("attachments", NullValueHandling = NullValueHandling.Ignore)]
public List<DiscordAttachment> Attachments { get; set; }
}
/// <summary>
/// Represents a interactions application command callback data.
/// </summary>
internal class DiscordInteractionApplicationCommandModalCallbackData
{
/// <summary>
/// Gets the custom id.
/// </summary>
[JsonProperty("custom_id", NullValueHandling = NullValueHandling.Ignore)]
public string CustomId { get; internal set; }
/// <summary>
/// Gets the content.
/// </summary>
[JsonProperty("title", NullValueHandling = NullValueHandling.Ignore)]
public string Title { get; internal set; }
/// <summary>
/// Gets the components.
/// </summary>
[JsonProperty("components", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyCollection<DiscordComponent> ModalComponents { get; internal set; }
}
diff --git a/DisCatSharp/Entities/Interaction/DiscordInteractionData.cs b/DisCatSharp/Entities/Interaction/DiscordInteractionData.cs
index f489215bf..58338e362 100644
--- a/DisCatSharp/Entities/Interaction/DiscordInteractionData.cs
+++ b/DisCatSharp/Entities/Interaction/DiscordInteractionData.cs
@@ -1,98 +1,98 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents the inner data payload of a <see cref="DiscordInteraction"/>.
/// </summary>
public sealed class DiscordInteractionData : SnowflakeObject
{
/// <summary>
/// Gets the name of the invoked interaction.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; internal set; }
/// <summary>
/// Gets the parameters and values of the invoked interaction.
/// </summary>
[JsonProperty("options", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<DiscordInteractionDataOption> Options { get; internal set; }
/// <summary>
/// Gets the component rows (Applicable to modal submits).
/// </summary>
[JsonProperty("components", NullValueHandling = NullValueHandling.Ignore)]
internal List<DiscordActionRowComponentResult> ComponentsInternal { get; set; }
[JsonIgnore]
public IReadOnlyList<DiscordComponentResult> Components
=> this.ComponentsInternal.Select(x => x.Components[0]).ToList();
/// <summary>
/// Gets the Discord snowflake objects resolved from this interaction's arguments.
/// </summary>
[JsonProperty("resolved", NullValueHandling = NullValueHandling.Ignore)]
public DiscordInteractionResolvedCollection Resolved { get; internal set; }
/// <summary>
/// The Id of the component that invoked this interaction, if applicable.
/// </summary>
[JsonProperty("custom_id", NullValueHandling = NullValueHandling.Ignore)]
public string CustomId { get; internal set; }
/// <summary>
/// The Id of the target. Applicable for context menus.
/// </summary>
[JsonProperty("target_id", NullValueHandling = NullValueHandling.Ignore)]
internal ulong? Target { get; set; }
/// <summary>
/// The type of component that invoked this interaction, if applicable.
/// </summary>
[JsonProperty("component_type", NullValueHandling = NullValueHandling.Ignore)]
public ComponentType ComponentType { get; internal set; }
/// <summary>
/// Gets the values of the interaction.
/// </summary>
[JsonProperty("values", NullValueHandling = NullValueHandling.Ignore)]
public string[] Values { get; internal set; } = Array.Empty<string>();
/// <summary>
/// Gets the type of the interaction.
/// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
public ApplicationCommandType Type { get; internal set; }
[JsonProperty("guild_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? GuildId { get; internal set; }
}
diff --git a/DisCatSharp/Entities/Interaction/DiscordInteractionDataOption.cs b/DisCatSharp/Entities/Interaction/DiscordInteractionDataOption.cs
index 25a757935..fa2c1b5d8 100644
--- a/DisCatSharp/Entities/Interaction/DiscordInteractionDataOption.cs
+++ b/DisCatSharp/Entities/Interaction/DiscordInteractionDataOption.cs
@@ -1,89 +1,89 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents parameters for interaction commands.
/// </summary>
public sealed class DiscordInteractionDataOption
{
/// <summary>
/// Gets the name of this interaction parameter.
/// </summary>
[JsonProperty("name")]
public string Name { get; internal set; }
/// <summary>
/// Gets the type of this interaction parameter.
/// </summary>
[JsonProperty("type")]
public ApplicationCommandOptionType Type { get; internal set; }
/// <summary>
/// Whether this option is currently focused by the user.
/// Only applicable for autocomplete option choices.
/// </summary>
[JsonProperty("focused")]
public bool Focused { get; internal set; }
/// <summary>
/// Gets the value of this interaction parameter.
/// </summary>
[JsonProperty("value")]
internal string RawValue { get; set; }
/// <summary>
/// Gets the value of this interaction parameter.
/// <para>This can be cast to a <see langword="long"/>, <see langword="bool"></see>, <see langword="string"></see>, <see langword="double"></see> or <see langword="ulong"/> depending on the <see cref="System.Type"/></para>
/// </summary>
[JsonIgnore]
public object Value =>
this.Type == ApplicationCommandOptionType.Integer && int.TryParse(this.RawValue, out var raw)
? raw
: this.Type == ApplicationCommandOptionType.Integer
? long.Parse(this.RawValue)
: this.Type switch
{
ApplicationCommandOptionType.Boolean => bool.Parse(this.RawValue),
ApplicationCommandOptionType.String => this.RawValue,
ApplicationCommandOptionType.Channel => ulong.Parse(this.RawValue),
ApplicationCommandOptionType.User => ulong.Parse(this.RawValue),
ApplicationCommandOptionType.Role => ulong.Parse(this.RawValue),
ApplicationCommandOptionType.Mentionable => ulong.Parse(this.RawValue),
ApplicationCommandOptionType.Number => double.Parse(this.RawValue),
ApplicationCommandOptionType.Attachment => ulong.Parse(this.RawValue),
_ => this.RawValue,
};
/// <summary>
/// Gets the additional parameters if this parameter is a subcommand.
/// </summary>
[JsonProperty("options", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<DiscordInteractionDataOption> Options { get; internal set; }
}
diff --git a/DisCatSharp/Entities/Interaction/DiscordInteractionModalBuilder.cs b/DisCatSharp/Entities/Interaction/DiscordInteractionModalBuilder.cs
index 4e8f8c36c..78a25345b 100644
--- a/DisCatSharp/Entities/Interaction/DiscordInteractionModalBuilder.cs
+++ b/DisCatSharp/Entities/Interaction/DiscordInteractionModalBuilder.cs
@@ -1,183 +1,183 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
namespace DisCatSharp.Entities;
/// <summary>
/// Constructs an interaction modal response.
/// </summary>
public sealed class DiscordInteractionModalBuilder
{
/// <summary>
/// Title of modal.
/// </summary>
public string Title
{
get => this._title;
set
{
if (value != null && value.Length > 128)
throw new ArgumentException("Title length cannot exceed 128 characters.", nameof(value));
this._title = value;
}
}
private string _title;
/// <summary>
/// Custom id of modal.
/// </summary>
public string CustomId { get; set; }
/// <summary>
/// Components to send on this interaction response.
/// </summary>
public IReadOnlyList<DiscordActionRowComponent> ModalComponents => this._components;
private readonly List<DiscordActionRowComponent> _components = new();
/// <summary>
/// Constructs a new empty interaction modal builder.
/// </summary>
public DiscordInteractionModalBuilder(string title = null, string customId = null)
{
this.Title = title ?? "Title";
this.CustomId = customId ?? Guid.NewGuid().ToString();
}
public DiscordInteractionModalBuilder WithTitle(string title)
{
this.Title = title;
return this;
}
public DiscordInteractionModalBuilder WithCustomId(string customId)
{
this.CustomId = customId;
return this;
}
/// <summary>
/// Appends a collection of text components to the builder. Each call will append to a new row.
/// </summary>
/// <param name="components">The components to append. Up to five.</param>
/// <returns>The current builder to chain calls with.</returns>
/// <exception cref="ArgumentException">Thrown when passing more than 5 components.</exception>
public DiscordInteractionModalBuilder AddTextComponents(params DiscordTextComponent[] components)
=> this.AddModalComponents(components);
/// <summary>
/// Appends a collection of select components to the builder. Each call will append to a new row.
/// </summary>
/// <param name="components">The components to append. Up to five.</param>
/// <returns>The current builder to chain calls with.</returns>
/// <exception cref="ArgumentException">Thrown when passing more than 5 components.</exception>
public DiscordInteractionModalBuilder AddSelectComponents(params DiscordBaseSelectComponent[] components)
=> this.AddModalComponents(components);
/// <summary>
/// Appends a text component to the builder.
/// </summary>
/// <param name="component">The component to append.</param>
/// <returns>The current builder to chain calls with.</returns>
public DiscordInteractionModalBuilder AddTextComponent(DiscordTextComponent component)
=> this.AddModalComponents(component);
/// <summary>
/// Appends a select component to the builder.
/// </summary>
/// <param name="component">The component to append.</param>
/// <returns>The current builder to chain calls with.</returns>
public DiscordInteractionModalBuilder AddSelectComponent(DiscordBaseSelectComponent component)
=> this.AddModalComponents(component);
/// <summary>
/// Appends a collection of components to the builder.
/// </summary>
/// <param name="components">The components to append. Up to five.</param>
/// <returns>The current builder to chain calls with.</returns>
/// <exception cref="ArgumentException">Thrown when passing more than 5 components.</exception>
public DiscordInteractionModalBuilder AddModalComponents(params DiscordComponent[] components)
{
var ara = components.ToArray();
if (ara.Length > 5)
throw new ArgumentException("You can only add 5 components to modals.");
if (this._components.Count + ara.Length > 5)
throw new ArgumentException($"You try to add too many components. We already have {this._components.Count}.");
foreach (var ar in ara)
this._components.Add(new DiscordActionRowComponent(new List<DiscordComponent>() { ar }));
return this;
}
/// <summary>
/// Appends several rows of components to the message
/// </summary>
/// <param name="components">The rows of components to add, holding up to five each.</param>
/// <returns>The current builder to chain calls with.</returns>
/// <exception cref="ArgumentException">Thrown when passing more than 5 components.</exception>
public DiscordInteractionModalBuilder AddModalComponents(IEnumerable<DiscordActionRowComponent> 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;
}
/// <summary>
/// Appends a collection of components to the builder. Each call will append to a new row.
/// </summary>
/// <param name="component">The component to append.</param>
/// <returns>The current builder to chain calls with.</returns>
internal DiscordInteractionModalBuilder AddModalComponents(DiscordComponent component)
{
this._components.Add(new DiscordActionRowComponent(new List<DiscordComponent>() { component }));
return this;
}
/// <summary>
/// Clears all message components on this builder.
/// </summary>
public void ClearComponents()
=> this._components.Clear();
/// <summary>
/// Allows for clearing the Interaction Response Builder so that it can be used again to send a new response.
/// </summary>
public void Clear()
{
this._components.Clear();
this.Title = null;
this.CustomId = null;
}
}
diff --git a/DisCatSharp/Entities/Interaction/DiscordInteractionResolvedCollection.cs b/DisCatSharp/Entities/Interaction/DiscordInteractionResolvedCollection.cs
index 7dcd429a6..192f1c685 100644
--- a/DisCatSharp/Entities/Interaction/DiscordInteractionResolvedCollection.cs
+++ b/DisCatSharp/Entities/Interaction/DiscordInteractionResolvedCollection.cs
@@ -1,69 +1,69 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a collection of Discord snowflake objects resolved from interaction arguments.
/// </summary>
public sealed class DiscordInteractionResolvedCollection
{
/// <summary>
/// Gets the resolved user objects, if any.
/// </summary>
[JsonProperty("users", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyDictionary<ulong, DiscordUser> Users { get; internal set; }
/// <summary>
/// Gets the resolved member objects, if any.
/// </summary>
[JsonProperty("members", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyDictionary<ulong, DiscordMember> Members { get; internal set; }
/// <summary>
/// Gets the resolved channel objects, if any.
/// </summary>
[JsonProperty("channels", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyDictionary<ulong, DiscordChannel> Channels { get; internal set; }
/// <summary>
/// Gets the resolved role objects, if any.
/// </summary>
[JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyDictionary<ulong, DiscordRole> Roles { get; internal set; }
/// <summary>
/// Gets the resolved message objects, if any.
/// </summary>
[JsonProperty("messages", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyDictionary<ulong, DiscordMessage> Messages { get; internal set; }
/// <summary>
/// Gets the resolved attachments objects, if any.
/// </summary>
[JsonProperty("attachments", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyDictionary<ulong, DiscordAttachment> Attachments { get; internal set; }
}
diff --git a/DisCatSharp/Entities/Interaction/DiscordInteractionResponseBuilder.cs b/DisCatSharp/Entities/Interaction/DiscordInteractionResponseBuilder.cs
index 43a72f6d0..16470d980 100644
--- a/DisCatSharp/Entities/Interaction/DiscordInteractionResponseBuilder.cs
+++ b/DisCatSharp/Entities/Interaction/DiscordInteractionResponseBuilder.cs
@@ -1,352 +1,352 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Constructs an interaction response.
/// </summary>
public sealed class DiscordInteractionResponseBuilder
{
/// <summary>
/// Whether this interaction response is text-to-speech.
/// </summary>
public bool IsTts { get; set; }
/// <summary>
/// Whether this interaction response should be ephemeral.
/// </summary>
public bool IsEphemeral { get; set; }
/// <summary>
/// Content of the message to send.
/// </summary>
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;
/// <summary>
/// Embeds to send on this interaction response.
/// </summary>
public IReadOnlyList<DiscordEmbed> Embeds => this._embeds;
private readonly List<DiscordEmbed> _embeds = new();
/// <summary>
/// Files to send on this interaction response.
/// </summary>
public IReadOnlyList<DiscordMessageFile> Files => this._files;
private readonly List<DiscordMessageFile> _files = new();
/// <summary>
/// Components to send on this interaction response.
/// </summary>
public IReadOnlyList<DiscordActionRowComponent> Components => this._components;
private readonly List<DiscordActionRowComponent> _components = new();
/// <summary>
/// The choices to send on this interaction response.
/// Mutually exclusive with content, embed, and components.
/// </summary>
public IReadOnlyList<DiscordApplicationCommandAutocompleteChoice> Choices => this._choices;
private readonly List<DiscordApplicationCommandAutocompleteChoice> _choices = new();
/// <summary>
/// Mentions to send on this interaction response.
/// </summary>
public IReadOnlyList<IMention> Mentions => this._mentions;
private readonly List<IMention> _mentions = new();
/// <summary>
/// Constructs a new empty interaction response builder.
/// </summary>
public DiscordInteractionResponseBuilder() { }
/// <summary>
/// Constructs a new <see cref="DiscordInteractionResponseBuilder"/> based on an existing <see cref="DisCatSharp.Entities.DiscordMessageBuilder"/>.
/// </summary>
/// <param name="builder">The builder to copy.</param>
public DiscordInteractionResponseBuilder(DiscordMessageBuilder builder)
{
this._content = builder.Content;
this._mentions = builder.Mentions;
this._embeds.AddRange(builder.Embeds);
this._components.AddRange(builder.Components);
}
/// <summary>
/// Appends a collection of components to the builder. Each call will append to a new row.
/// </summary>
/// <param name="components">The components to append. Up to five.</param>
/// <returns>The current builder to chain calls with.</returns>
/// <exception cref="ArgumentException">Thrown when passing more than 5 components.</exception>
public DiscordInteractionResponseBuilder AddComponents(params DiscordComponent[] components)
=> this.AddComponents((IEnumerable<DiscordComponent>)components);
/// <summary>
/// Appends several rows of components to the message
/// </summary>
/// <param name="components">The rows of components to add, holding up to five each.</param>
/// <returns></returns>
public DiscordInteractionResponseBuilder AddComponents(IEnumerable<DiscordActionRowComponent> 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;
}
/// <summary>
/// Appends a collection of components to the builder. Each call will append to a new row.
/// </summary>
/// <param name="components">The components to append. Up to five.</param>
/// <returns>The current builder to chain calls with.</returns>
/// <exception cref="ArgumentException">Thrown when passing more than 5 components.</exception>
public DiscordInteractionResponseBuilder AddComponents(IEnumerable<DiscordComponent> 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;
}
/// <summary>
/// Indicates if the interaction response will be text-to-speech.
/// </summary>
/// <param name="tts">Text-to-speech</param>
public DiscordInteractionResponseBuilder WithTts(bool tts)
{
this.IsTts = tts;
return this;
}
/// <summary>
/// Sets the interaction response to be ephemeral.
/// </summary>
/// <param name="ephemeral">Whether the response should be ephemeral. Defaults to true.</param>
public DiscordInteractionResponseBuilder AsEphemeral(bool ephemeral = true)
{
this.IsEphemeral = ephemeral;
return this;
}
/// <summary>
/// Sets the content of the message to send.
/// </summary>
/// <param name="content">Content to send.</param>
public DiscordInteractionResponseBuilder WithContent(string content)
{
this.Content = content;
return this;
}
/// <summary>
/// Adds an embed to send with the interaction response.
/// </summary>
/// <param name="embed">Embed to add.</param>
public DiscordInteractionResponseBuilder AddEmbed(DiscordEmbed embed)
{
if (embed != null)
this._embeds.Add(embed); // Interactions will 400 silently //
return this;
}
/// <summary>
/// Adds the given embeds to send with the interaction response.
/// </summary>
/// <param name="embeds">Embeds to add.</param>
public DiscordInteractionResponseBuilder AddEmbeds(IEnumerable<DiscordEmbed> embeds)
{
this._embeds.AddRange(embeds);
return this;
}
/// <summary>
/// Adds a file to the interaction response.
/// </summary>
/// <param name="filename">Name of the file.</param>
/// <param name="data">File data.</param>
/// <param name="resetStreamPosition">Tells the API Client to reset the stream position to what it was after the file is sent.</param>
/// <param name="description">Description of the file.</param>
/// <returns>The builder to chain calls with.</returns>
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;
}
/// <summary>
/// Sets if the message has files to be sent.
/// </summary>
/// <param name="stream">The Stream to the file.</param>
/// <param name="resetStreamPosition">Tells the API Client to reset the stream position to what it was after the file is sent.</param>
/// <param name="description">Description of the file.</param>
/// <returns>The builder to chain calls with.</returns>
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;
}
/// <summary>
/// Adds the given files to the interaction response builder.
/// </summary>
/// <param name="files">Dictionary of file name and file data.</param>
/// <param name="resetStreamPosition">Tells the API Client to reset the stream position to what it was after the file is sent.</param>
/// <returns>The builder to chain calls with.</returns>
public DiscordInteractionResponseBuilder AddFiles(Dictionary<string, Stream> 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;
}
/// <summary>
/// Adds the mention to the mentions to parse, etc. with the interaction response.
/// </summary>
/// <param name="mention">Mention to add.</param>
public DiscordInteractionResponseBuilder AddMention(IMention mention)
{
this._mentions.Add(mention);
return this;
}
/// <summary>
/// Adds the mentions to the mentions to parse, etc. with the interaction response.
/// </summary>
/// <param name="mentions">Mentions to add.</param>
public DiscordInteractionResponseBuilder AddMentions(IEnumerable<IMention> mentions)
{
this._mentions.AddRange(mentions);
return this;
}
/// <summary>
/// Adds a single auto-complete choice to the builder.
/// </summary>
/// <param name="choice">The choice to add.</param>
/// <returns>The current builder to chain calls with.</returns>
public DiscordInteractionResponseBuilder AddAutoCompleteChoice(DiscordApplicationCommandAutocompleteChoice choice)
{
this._choices.Add(choice);
return this;
}
/// <summary>
/// Adds auto-complete choices to the builder.
/// </summary>
/// <param name="choices">The choices to add.</param>
/// <returns>The current builder to chain calls with.</returns>
public DiscordInteractionResponseBuilder AddAutoCompleteChoices(IEnumerable<DiscordApplicationCommandAutocompleteChoice> choices)
{
this._choices.AddRange(choices);
return this;
}
/// <summary>
/// Adds auto-complete choices to the builder.
/// </summary>
/// <param name="choices">The choices to add.</param>
/// <returns>The current builder to chain calls with.</returns>
public DiscordInteractionResponseBuilder AddAutoCompleteChoices(params DiscordApplicationCommandAutocompleteChoice[] choices)
=> this.AddAutoCompleteChoices((IEnumerable<DiscordApplicationCommandAutocompleteChoice>)choices);
/// <summary>
/// Clears all message components on this builder.
/// </summary>
public void ClearComponents()
=> this._components.Clear();
/// <summary>
/// Allows for clearing the Interaction Response Builder so that it can be used again to send a new response.
/// </summary>
public void Clear()
{
this.Content = "";
this._embeds.Clear();
this.IsTts = false;
this.IsEphemeral = false;
this._mentions.Clear();
this._components.Clear();
this._choices.Clear();
this._files.Clear();
}
}
diff --git a/DisCatSharp/Entities/Invite/DiscordInvite.cs b/DisCatSharp/Entities/Invite/DiscordInvite.cs
index c92f63f5b..fd683736e 100644
--- a/DisCatSharp/Entities/Invite/DiscordInvite.cs
+++ b/DisCatSharp/Entities/Invite/DiscordInvite.cs
@@ -1,207 +1,207 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Threading.Tasks;
using DisCatSharp.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a Discord invite.
/// </summary>
public class DiscordInvite
{
/// <summary>
/// Gets the base client.
/// </summary>
internal BaseDiscordClient Discord { get; set; }
/// <summary>
/// Gets the invite's code.
/// </summary>
[JsonProperty("code", NullValueHandling = NullValueHandling.Ignore)]
public string Code { get; internal set; }
/// <summary>
/// Gets the invite's url.
/// </summary>
[JsonIgnore]
public string Url => DiscordDomain.GetDomain(CoreDomain.DiscordShortlink).Url + "/" + this.Code;
/// <summary>
/// Gets the invite's url as Uri.
/// </summary>
[JsonIgnore]
public Uri Uri => new(this.Url);
/// <summary>
/// Gets the guild this invite is for.
/// </summary>
[JsonProperty("guild", NullValueHandling = NullValueHandling.Ignore)]
public DiscordInviteGuild Guild { get; internal set; }
/// <summary>
/// Gets the channel this invite is for.
/// </summary>
[JsonProperty("channel", NullValueHandling = NullValueHandling.Ignore)]
public DiscordInviteChannel Channel { get; internal set; }
/// <summary>
/// Gets the target type for the voice channel this invite is for.
/// </summary>
[JsonProperty("target_type", NullValueHandling = NullValueHandling.Ignore)]
public TargetType? TargetType { get; internal set; }
/// <summary>
/// Gets the type of this invite.
/// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
public InviteType Type { get; internal set; }
/// <summary>
/// Gets the user that is currently livestreaming.
/// </summary>
[JsonProperty("target_user", NullValueHandling = NullValueHandling.Ignore)]
public DiscordUser TargetUser { get; internal set; }
/// <summary>
/// Gets the embedded partial application to open for this voice channel.
/// </summary>
[JsonProperty("target_application", NullValueHandling = NullValueHandling.Ignore)]
public DiscordApplication TargetApplication { get; internal set; }
/// <summary>
/// Gets the approximate guild online member count for the invite.
/// </summary>
[JsonProperty("approximate_presence_count", NullValueHandling = NullValueHandling.Ignore)]
public int? ApproximatePresenceCount { get; internal set; }
/// <summary>
/// Gets the approximate guild total member count for the invite.
/// </summary>
[JsonProperty("approximate_member_count", NullValueHandling = NullValueHandling.Ignore)]
public int? ApproximateMemberCount { get; internal set; }
/// <summary>
/// Gets the user who created the invite.
/// </summary>
[JsonProperty("inviter", NullValueHandling = NullValueHandling.Ignore)]
public DiscordUser Inviter { get; internal set; }
/// <summary>
/// Gets the number of times this invite has been used.
/// </summary>
[JsonProperty("uses", NullValueHandling = NullValueHandling.Ignore)]
public int Uses { get; internal set; }
/// <summary>
/// Gets the max number of times this invite can be used.
/// </summary>
[JsonProperty("max_uses", NullValueHandling = NullValueHandling.Ignore)]
public int MaxUses { get; internal set; }
/// <summary>
/// Gets duration in seconds after which the invite expires.
/// </summary>
[JsonProperty("max_age", NullValueHandling = NullValueHandling.Ignore)]
public int MaxAge { get; internal set; }
/// <summary>
/// Gets whether this invite only grants temporary membership.
/// </summary>
[JsonProperty("temporary", NullValueHandling = NullValueHandling.Ignore)]
public bool IsTemporary { get; internal set; }
/// <summary>
/// Gets the date and time this invite was created.
/// </summary>
[JsonProperty("created_at", NullValueHandling = NullValueHandling.Ignore)]
public DateTimeOffset CreatedAt { get; internal set; }
/// <summary>
/// Gets the date and time when this invite expires.
/// </summary>
[JsonProperty("expires_at", NullValueHandling = NullValueHandling.Ignore)]
public DateTimeOffset ExpiresAt { get; internal set; }
/// <summary>
/// Gets the date and time when this invite got expired.
/// </summary>
[JsonProperty("expired_at", NullValueHandling = NullValueHandling.Ignore)]
public DateTimeOffset ExpiredAt { get; internal set; }
/// <summary>
/// Gets whether this invite is revoked.
/// </summary>
[JsonProperty("revoked", NullValueHandling = NullValueHandling.Ignore)]
public bool IsRevoked { get; internal set; }
/// <summary>
/// Gets the stage instance this invite is for.
/// </summary>
[JsonProperty("stage_instance", NullValueHandling = NullValueHandling.Ignore)]
public DiscordInviteStage Stage { get; internal set; }
/// <summary>
/// Gets the guild scheduled event data for the invite.
/// </summary>
[JsonProperty("guild_scheduled_event", NullValueHandling = NullValueHandling.Ignore)]
public DiscordScheduledEvent GuildScheduledEvent { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordInvite"/> class.
/// </summary>
internal DiscordInvite()
{ }
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Deletes the invite.
/// </summary>
/// <param name="reason">Reason for audit logs.</param>
/// <returns></returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageChannels"/> permission or the <see cref="Permissions.ManageGuild"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the emoji does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordInvite> DeleteAsync(string reason = null)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> this.Discord.ApiClient.DeleteInviteAsync(this.Code, reason);
/// <summary>
/// Converts this invite into an invite link.
/// </summary>
/// <returns>A discord.gg invite link.</returns>
public override string ToString()
=> $"{DiscordDomain.GetDomain(CoreDomain.DiscordShortlink).Url}/{this.Code}";
}
diff --git a/DisCatSharp/Entities/Invite/DiscordInviteChannel.cs b/DisCatSharp/Entities/Invite/DiscordInviteChannel.cs
index 9a348c30f..973d6fdee 100644
--- a/DisCatSharp/Entities/Invite/DiscordInviteChannel.cs
+++ b/DisCatSharp/Entities/Invite/DiscordInviteChannel.cs
@@ -1,51 +1,51 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents the channel to which an invite is linked.
/// </summary>
public class DiscordInviteChannel : SnowflakeObject
{
/// <summary>
/// Gets the name of the channel.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; internal set; }
/// <summary>
/// Gets the type of the channel.
/// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
public ChannelType Type { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordInviteChannel"/> class.
/// </summary>
internal DiscordInviteChannel()
{ }
}
diff --git a/DisCatSharp/Entities/Invite/DiscordInviteGuild.cs b/DisCatSharp/Entities/Invite/DiscordInviteGuild.cs
index bf7f7a6d5..77de12eed 100644
--- a/DisCatSharp/Entities/Invite/DiscordInviteGuild.cs
+++ b/DisCatSharp/Entities/Invite/DiscordInviteGuild.cs
@@ -1,130 +1,130 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.Enums;
using DisCatSharp.Net;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a guild to which the user is invited.
/// </summary>
public class DiscordInviteGuild : SnowflakeObject
{
/// <summary>
/// Gets the name of the guild.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; internal set; }
/// <summary>
/// Gets the guild icon's hash.
/// </summary>
[JsonProperty("icon", NullValueHandling = NullValueHandling.Ignore)]
public string IconHash { get; internal set; }
/// <summary>
/// Gets the guild icon's url.
/// </summary>
[JsonIgnore]
public string IconUrl
=> !string.IsNullOrWhiteSpace(this.IconHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.ICONS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.IconHash}.jpg" : null;
/// <summary>
/// Gets the hash of guild's invite splash.
/// </summary>
[JsonProperty("splash", NullValueHandling = NullValueHandling.Ignore)]
internal string SplashHash { get; set; }
/// <summary>
/// Gets the URL of guild's invite splash.
/// </summary>
[JsonIgnore]
public string SplashUrl
=> !string.IsNullOrWhiteSpace(this.SplashHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.SPLASHES}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.SplashHash}.jpg" : null;
/// <summary>
/// Gets the guild's banner hash, when applicable.
/// </summary>
[JsonProperty("banner", NullValueHandling = NullValueHandling.Ignore)]
public string Banner { get; internal set; }
/// <summary>
/// Gets the guild's banner in url form.
/// </summary>
[JsonIgnore]
public string BannerUrl
=> !string.IsNullOrWhiteSpace(this.Banner) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.BANNERS}/{this.Id}/{this.Banner}" : null;
/// <summary>
/// Gets the guild description, when applicable.
/// </summary>
[JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)]
public string Description { get; internal set; }
/// <summary>
/// Gets a collection of this guild's features.
/// </summary>
[JsonProperty("features", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<string> Features { get; internal set; }
/// <summary>
/// Gets the guild's verification level.
/// </summary>
[JsonProperty("verification_level", NullValueHandling = NullValueHandling.Ignore)]
public VerificationLevel VerificationLevel { get; internal set; }
/// <summary>
/// Gets vanity URL code for this guild, when applicable.
/// </summary>
[JsonProperty("vanity_url_code")]
public string VanityUrlCode { get; internal set; }
/// <summary>
/// Gets the guild's welcome screen, when applicable.
/// </summary>
[JsonProperty("welcome_screen", NullValueHandling = NullValueHandling.Ignore)]
public DiscordGuildWelcomeScreen WelcomeScreen { get; internal set; }
/// <summary>
/// Gets the guild nsfw status.
/// </summary>
[JsonProperty("nsfw", NullValueHandling = NullValueHandling.Ignore)]
public bool IsNsfw { get; internal set; }
/// <summary>
/// Gets the guild nsfw level.
/// </summary>
[JsonProperty("nsfw_level", NullValueHandling = NullValueHandling.Ignore)]
public NsfwLevel NsfwLevel { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordInviteGuild"/> class.
/// </summary>
internal DiscordInviteGuild()
{ }
}
diff --git a/DisCatSharp/Entities/Invite/DiscordInviteStage.cs b/DisCatSharp/Entities/Invite/DiscordInviteStage.cs
index 7b608889a..819f658ca 100644
--- a/DisCatSharp/Entities/Invite/DiscordInviteStage.cs
+++ b/DisCatSharp/Entities/Invite/DiscordInviteStage.cs
@@ -1,72 +1,72 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a stage instance to which the user is invited.
/// </summary>
public class DiscordInviteStage : SnowflakeObject
{
/// <summary>
/// Gets the members speaking in the Stage.
/// </summary>
[JsonIgnore]
public IReadOnlyDictionary<ulong, DiscordMember> Members { get; internal set; }
[JsonProperty("members", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(SnowflakeArrayAsDictionaryJsonConverter))]
internal ConcurrentDictionary<ulong, DiscordMember> MembersInternal = new();
/// <summary>
/// Gets the number of users in the Stage.
/// </summary>
[JsonProperty("participant_count", NullValueHandling = NullValueHandling.Ignore)]
public int ParticipantCount { get; internal set; }
/// <summary>
/// Gets the number of users speaking in the Stage.
/// </summary>
[JsonProperty("speaker_count", NullValueHandling = NullValueHandling.Ignore)]
public int SpeakerCount { get; internal set; }
/// <summary>
/// Gets the topic of the Stage instance.
/// </summary>
[JsonProperty("topic", NullValueHandling = NullValueHandling.Ignore)]
public string Topic { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordInviteStage"/> class.
/// </summary>
internal DiscordInviteStage()
{
this.Members = new ReadOnlyConcurrentDictionary<ulong, DiscordMember>(this.MembersInternal);
}
}
diff --git a/DisCatSharp/Entities/Message/DiscordAttachment.cs b/DisCatSharp/Entities/Message/DiscordAttachment.cs
index cf6162ce8..93e9d7ce0 100644
--- a/DisCatSharp/Entities/Message/DiscordAttachment.cs
+++ b/DisCatSharp/Entities/Message/DiscordAttachment.cs
@@ -1,93 +1,93 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents an attachment for a message.
/// </summary>
public class DiscordAttachment : SnowflakeObject
{
/// <summary>
/// Gets the name of the file.
/// </summary>
[JsonProperty("filename", NullValueHandling = NullValueHandling.Ignore)]
public string FileName { get; internal set; }
/// <summary>
/// Gets the description of the file.
/// </summary>
[JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)]
public string Description { get; set; }
/// <summary>
/// Gets the media, or MIME, type of the file.
/// </summary>
[JsonProperty("content_type", NullValueHandling = NullValueHandling.Ignore)]
public string MediaType { get; internal set; }
/// <summary>
/// Gets the file size in bytes.
/// </summary>
[JsonProperty("size", NullValueHandling = NullValueHandling.Ignore)]
public int? FileSize { get; internal set; }
/// <summary>
/// Gets the URL of the file.
/// </summary>
[JsonProperty("url", NullValueHandling = NullValueHandling.Ignore)]
public string Url { get; internal set; }
/// <summary>
/// Gets the proxied URL of the file.
/// </summary>
[JsonProperty("proxy_url", NullValueHandling = NullValueHandling.Ignore)]
public string ProxyUrl { get; internal set; }
/// <summary>
/// Gets the height. Applicable only if the attachment is an image.
/// </summary>
[JsonProperty("height", NullValueHandling = NullValueHandling.Ignore)]
public int? Height { get; internal set; }
/// <summary>
/// Gets the width. Applicable only if the attachment is an image.
/// </summary>
[JsonProperty("width", NullValueHandling = NullValueHandling.Ignore)]
public int? Width { get; internal set; }
/// <summary>
/// Gets whether this attachment is ephemeral.
/// Ephemeral attachments will automatically be removed after a set period of time.
/// Ephemeral attachments on messages are guaranteed to be available as long as the message itself exists.
/// </summary>
[JsonProperty("ephemeral", NullValueHandling = NullValueHandling.Ignore)]
public bool? Ephemeral { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordAttachment"/> class.
/// </summary>
internal DiscordAttachment()
{ }
}
diff --git a/DisCatSharp/Entities/Message/DiscordMentions.cs b/DisCatSharp/Entities/Message/DiscordMentions.cs
index 5f03b6a8c..6812df55c 100644
--- a/DisCatSharp/Entities/Message/DiscordMentions.cs
+++ b/DisCatSharp/Entities/Message/DiscordMentions.cs
@@ -1,142 +1,142 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Handles mentionables.
/// </summary>
internal class DiscordMentions
{
/// <summary>
/// Parse users.
/// </summary>
private const string PARSE_USERS = "users";
/// <summary>
/// Parse roles.
/// </summary>
private const string PARSE_ROLES = "roles";
/// <summary>
/// Parse everyone.
/// </summary>
private const string PARSE_EVERYONE = "everyone";
/// <summary>
/// Collection roles to serialize
/// </summary>
[JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<ulong> Roles { get; }
/// <summary>
/// Collection of users to serialize
/// </summary>
[JsonProperty("users", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<ulong> Users { get; }
/// <summary>
/// The values to be parsed
/// </summary>
[JsonProperty("parse", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<string> Parse { get; }
/// <summary>
/// For replies, whether to mention the author of the message being replied to.
/// </summary>
[JsonProperty("replied_user", NullValueHandling = NullValueHandling.Ignore)]
public bool? RepliedUser { get; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordMentions"/> class.
/// </summary>
/// <param name="mentions">The mentions.</param>
/// <param name="mention">If true, mention.</param>
/// <param name="repliedUser">If true, replied user.</param>
internal DiscordMentions(IEnumerable<IMention> mentions, bool mention = false, bool repliedUser = false)
{
if (mentions == null)
return;
if (!mentions.Any())
{
this.Parse = Array.Empty<string>();
this.RepliedUser = repliedUser;
return;
}
if (mention)
{
this.RepliedUser = repliedUser;
}
var roles = new HashSet<ulong>();
var users = new HashSet<ulong>();
var parse = new HashSet<string>();
foreach (var m in mentions)
{
switch (m)
{
default:
throw new NotSupportedException("Type not supported in mentions.");
case UserMention u:
if (u.Id.HasValue)
users.Add(u.Id.Value);
else
parse.Add(PARSE_USERS);
break;
case RoleMention r:
if (r.Id.HasValue)
roles.Add(r.Id.Value);
else
parse.Add(PARSE_ROLES);
break;
case EveryoneMention e:
parse.Add(PARSE_EVERYONE);
break;
case RepliedUserMention _:
this.RepliedUser = repliedUser;
break;
}
}
if (!parse.Contains(PARSE_USERS) && users.Count > 0)
this.Users = users.ToArray();
if (!parse.Contains(PARSE_ROLES) && roles.Count > 0)
this.Roles = roles.ToArray();
if (parse.Count > 0)
this.Parse = parse.ToArray();
}
}
diff --git a/DisCatSharp/Entities/Message/DiscordMessage.cs b/DisCatSharp/Entities/Message/DiscordMessage.cs
index c31fcf48f..17d39a1a9 100644
--- a/DisCatSharp/Entities/Message/DiscordMessage.cs
+++ b/DisCatSharp/Entities/Message/DiscordMessage.cs
@@ -1,1083 +1,1083 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a Discord text message.
/// </summary>
public class DiscordMessage : SnowflakeObject, IEquatable<DiscordMessage>
{
/// <summary>
/// Initializes a new instance of the <see cref="DiscordMessage"/> class.
/// </summary>
internal DiscordMessage()
{
this._attachmentsLazy = new Lazy<IReadOnlyList<DiscordAttachment>>(() => new ReadOnlyCollection<DiscordAttachment>(this.AttachmentsInternal));
this._embedsLazy = new Lazy<IReadOnlyList<DiscordEmbed>>(() => new ReadOnlyCollection<DiscordEmbed>(this.EmbedsInternal));
this._mentionedChannelsLazy = new Lazy<IReadOnlyList<DiscordChannel>>(() => this.MentionedChannelsInternal != null
? new ReadOnlyCollection<DiscordChannel>(this.MentionedChannelsInternal)
: Array.Empty<DiscordChannel>());
this._mentionedRolesLazy = new Lazy<IReadOnlyList<DiscordRole>>(() => this.MentionedRolesInternal != null ? new ReadOnlyCollection<DiscordRole>(this.MentionedRolesInternal) : Array.Empty<DiscordRole>());
this.MentionedUsersLazy = new Lazy<IReadOnlyList<DiscordUser>>(() => new ReadOnlyCollection<DiscordUser>(this.MentionedUsersInternal));
this._reactionsLazy = new Lazy<IReadOnlyList<DiscordReaction>>(() => new ReadOnlyCollection<DiscordReaction>(this.ReactionsInternal));
this._stickersLazy = new Lazy<IReadOnlyList<DiscordSticker>>(() => new ReadOnlyCollection<DiscordSticker>(this.StickersInternal));
this._jumpLink = new Lazy<Uri>(() =>
{
string gid = null;
if (this.Channel != null)
gid = this.Channel is DiscordDmChannel
? "@me"
: this.Channel is DiscordThreadChannel
? this.INTERNAL_THREAD.GuildId.Value.ToString(CultureInfo.InvariantCulture)
: this.GuildId.HasValue
? this.GuildId.Value.ToString(CultureInfo.InvariantCulture)
: this.Channel.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" : this.Discord.Configuration.UsePtb ? "ptb.discord.com" : "discord.com")}/channels/{gid}/{cid}/{mid}");
});
}
/// <summary>
/// Initializes a new instance of the <see cref="DiscordMessage"/> class.
/// </summary>
/// <param name="other">The other message.</param>
internal DiscordMessage(DiscordMessage other)
: this()
{
this.Discord = other.Discord;
this.AttachmentsInternal = other.AttachmentsInternal; // the attachments cannot change, thus no need to copy and reallocate.
this.EmbedsInternal = new List<DiscordEmbed>(other.EmbedsInternal);
if (other.MentionedChannelsInternal != null)
this.MentionedChannelsInternal = new List<DiscordChannel>(other.MentionedChannelsInternal);
if (other.MentionedRolesInternal != null)
this.MentionedRolesInternal = new List<DiscordRole>(other.MentionedRolesInternal);
if (other.MentionedRoleIds != null)
this.MentionedRoleIds = new List<ulong>(other.MentionedRoleIds);
this.MentionedUsersInternal = new List<DiscordUser>(other.MentionedUsersInternal);
this.ReactionsInternal = new List<DiscordReaction>(other.ReactionsInternal);
this.StickersInternal = new List<DiscordSticker>(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.MessageType = other.MessageType;
this.Pinned = other.Pinned;
this.TimestampRaw = other.TimestampRaw;
this.WebhookId = other.WebhookId;
this.GuildId = other.GuildId;
}
/// <summary>
/// Gets the channel in which the message was sent.
/// </summary>
[JsonIgnore]
public DiscordChannel Channel
{
get => (this.Discord as DiscordClient)?.InternalGetCachedChannel(this.ChannelId, this.GuildId) ?? this._channel;
internal set => this._channel = value;
}
private DiscordChannel _channel;
/// <summary>
/// Gets the thread in which the message was sent.
/// </summary>
[JsonIgnore]
private DiscordThreadChannel INTERNAL_THREAD
{
get => (this.Discord as DiscordClient)?.InternalGetCachedThread(this.ChannelId) ?? this._thread;
set => this._thread = value;
}
private DiscordThreadChannel _thread;
/// <summary>
/// Gets the ID of the channel in which the message was sent.
/// </summary>
[JsonProperty("channel_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong ChannelId { get; internal set; }
/// <summary>
/// Gets the components this message was sent with.
/// </summary>
[JsonProperty("components", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyCollection<DiscordActionRowComponent> Components { get; internal set; }
/// <summary>
/// Gets the user or member that sent the message.
/// </summary>
[JsonProperty("author", NullValueHandling = NullValueHandling.Ignore)]
public DiscordUser Author { get; internal set; }
/// <summary>
/// Gets the message's content.
/// </summary>
[JsonProperty("content", NullValueHandling = NullValueHandling.Ignore)]
public string Content { get; internal set; }
/// <summary>
/// Gets the message's creation timestamp.
/// </summary>
[JsonIgnore]
public DateTimeOffset Timestamp
=> !string.IsNullOrWhiteSpace(this.TimestampRaw) && DateTimeOffset.TryParse(this.TimestampRaw, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dto) ?
dto : this.CreationTimestamp;
/// <summary>
/// Gets the message's creation timestamp as raw string.
/// </summary>
[JsonProperty("timestamp", NullValueHandling = NullValueHandling.Ignore)]
internal string TimestampRaw { get; set; }
/// <summary>
/// Gets the message's edit timestamp. Will be null if the message was not edited.
/// </summary>
[JsonIgnore]
public DateTimeOffset? EditedTimestamp
=> !string.IsNullOrWhiteSpace(this.EditedTimestampRaw) && DateTimeOffset.TryParse(this.EditedTimestampRaw, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dto) ?
dto : null;
/// <summary>
/// Gets the message's edit timestamp as raw string. Will be null if the message was not edited.
/// </summary>
[JsonProperty("edited_timestamp", NullValueHandling = NullValueHandling.Ignore)]
internal string EditedTimestampRaw { get; set; }
/// <summary>
/// Gets whether this message was edited.
/// </summary>
[JsonIgnore]
public bool IsEdited
=> !string.IsNullOrWhiteSpace(this.EditedTimestampRaw);
/// <summary>
/// Gets whether the message is a text-to-speech message.
/// </summary>
[JsonProperty("tts", NullValueHandling = NullValueHandling.Ignore)]
public bool IsTts { get; internal set; }
/// <summary>
/// Gets whether the message mentions everyone.
/// </summary>
[JsonProperty("mention_everyone", NullValueHandling = NullValueHandling.Ignore)]
public bool MentionEveryone { get; internal set; }
/// <summary>
/// Gets users or members mentioned by this message.
/// </summary>
[JsonIgnore]
public IReadOnlyList<DiscordUser> MentionedUsers
=> this.MentionedUsersLazy.Value;
[JsonProperty("mentions", NullValueHandling = NullValueHandling.Ignore)]
internal List<DiscordUser> MentionedUsersInternal;
[JsonIgnore]
internal readonly Lazy<IReadOnlyList<DiscordUser>> 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...
/// <summary>
/// Gets roles mentioned by this message.
/// </summary>
[JsonIgnore]
public IReadOnlyList<DiscordRole> MentionedRoles
=> this._mentionedRolesLazy.Value;
[JsonIgnore]
internal List<DiscordRole> MentionedRolesInternal;
[JsonProperty("mention_roles")]
internal List<ulong> MentionedRoleIds;
[JsonIgnore]
private readonly Lazy<IReadOnlyList<DiscordRole>> _mentionedRolesLazy;
/// <summary>
/// Gets channels mentioned by this message.
/// </summary>
[JsonIgnore]
public IReadOnlyList<DiscordChannel> MentionedChannels
=> this._mentionedChannelsLazy.Value;
[JsonIgnore]
internal List<DiscordChannel> MentionedChannelsInternal;
[JsonIgnore]
private readonly Lazy<IReadOnlyList<DiscordChannel>> _mentionedChannelsLazy;
/// <summary>
/// Gets files attached to this message.
/// </summary>
[JsonIgnore]
public IReadOnlyList<DiscordAttachment> Attachments
=> this._attachmentsLazy.Value;
[JsonProperty("attachments", NullValueHandling = NullValueHandling.Ignore)]
internal List<DiscordAttachment> AttachmentsInternal = new();
[JsonIgnore]
private readonly Lazy<IReadOnlyList<DiscordAttachment>> _attachmentsLazy;
/// <summary>
/// Gets embeds attached to this message.
/// </summary>
[JsonIgnore]
public IReadOnlyList<DiscordEmbed> Embeds
=> this._embedsLazy.Value;
[JsonProperty("embeds", NullValueHandling = NullValueHandling.Ignore)]
internal List<DiscordEmbed> EmbedsInternal = new();
[JsonIgnore]
private readonly Lazy<IReadOnlyList<DiscordEmbed>> _embedsLazy;
/// <summary>
/// Gets reactions used on this message.
/// </summary>
[JsonIgnore]
public IReadOnlyList<DiscordReaction> Reactions
=> this._reactionsLazy.Value;
[JsonProperty("reactions", NullValueHandling = NullValueHandling.Ignore)]
internal List<DiscordReaction> ReactionsInternal = new();
[JsonIgnore]
private readonly Lazy<IReadOnlyList<DiscordReaction>> _reactionsLazy;
/// <summary>
/// Gets the nonce sent with the message, if the message was sent by the client.
/// </summary>
[JsonProperty("nonce", NullValueHandling = NullValueHandling.Ignore)]
public string Nonce { get; internal set; }
/// <summary>
/// Gets whether the message is pinned.
/// </summary>
[JsonProperty("pinned", NullValueHandling = NullValueHandling.Ignore)]
public bool Pinned { get; internal set; }
/// <summary>
/// Gets the id of the webhook that generated this message.
/// </summary>
[JsonProperty("webhook_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? WebhookId { get; internal set; }
/// <summary>
/// Gets the type of the message.
/// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
public MessageType? MessageType { get; internal set; }
/// <summary>
/// Gets the message activity in the Rich Presence embed.
/// </summary>
[JsonProperty("activity", NullValueHandling = NullValueHandling.Ignore)]
public DiscordMessageActivity Activity { get; internal set; }
/// <summary>
/// Gets the message application in the Rich Presence embed.
/// </summary>
[JsonProperty("application", NullValueHandling = NullValueHandling.Ignore)]
public DiscordMessageApplication Application { get; internal set; }
/// <summary>
/// Gets the message application id in the Rich Presence embed.
/// </summary>
[JsonProperty("application_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong ApplicationId { get; internal set; }
/// <summary>
/// Gets the internal reference.
/// </summary>
[JsonProperty("message_reference", NullValueHandling = NullValueHandling.Ignore)]
internal InternalDiscordMessageReference? InternalReference { get; set; }
/// <summary>
/// Gets the original message reference from the crossposted message.
/// </summary>
[JsonIgnore]
public DiscordMessageReference Reference
=> this.InternalReference.HasValue ? this?.InternalBuildMessageReference() : null;
/// <summary>
/// Gets the bitwise flags for this message.
/// </summary>
[JsonProperty("flags", NullValueHandling = NullValueHandling.Ignore)]
public MessageFlags? Flags { get; internal set; }
/// <summary>
/// Gets whether the message originated from a webhook.
/// </summary>
[JsonIgnore]
public bool WebhookMessage
=> this.WebhookId != null;
/// <summary>
/// Gets the jump link to this message.
/// </summary>
[JsonIgnore]
public Uri JumpLink => this._jumpLink.Value;
private readonly Lazy<Uri> _jumpLink;
/// <summary>
/// Gets stickers for this message.
/// </summary>
[JsonIgnore]
public IReadOnlyList<DiscordSticker> Stickers
=> this._stickersLazy.Value;
[JsonProperty("sticker_items", NullValueHandling = NullValueHandling.Ignore)]
internal List<DiscordSticker> StickersInternal = new();
[JsonIgnore]
private readonly Lazy<IReadOnlyList<DiscordSticker>> _stickersLazy;
/// <summary>
/// Gets the guild id.
/// </summary>
[JsonProperty("guild_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? GuildId { get; internal set; }
/// <summary>
/// Gets the guild to which this channel belongs.
/// </summary>
[JsonIgnore]
public DiscordGuild Guild
=> this.GuildId.HasValue && this.Discord.Guilds.TryGetValue(this.GuildId.Value, out var guild) ? guild : null;
/// <summary>
/// Gets the message object for the referenced message
/// </summary>
[JsonProperty("referenced_message", NullValueHandling = NullValueHandling.Ignore)]
public DiscordMessage ReferencedMessage { get; internal set; }
/// <summary>
/// Gets whether the message is a response to an interaction.
/// </summary>
[JsonProperty("interaction", NullValueHandling = NullValueHandling.Ignore)]
public DiscordMessageInteraction Interaction { get; internal set; }
/// <summary>
/// Gets the thread that was started from this message.
/// </summary>
[JsonProperty("thread", NullValueHandling = NullValueHandling.Ignore)]
public DiscordThreadChannel Thread { get; internal set; }
/// <summary>
/// Build the message reference.
/// </summary>
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.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;
}
/// <summary>
/// Gets the mentions.
/// </summary>
/// <returns>An array of IMentions.</returns>
private List<IMention> GetMentions()
{
var mentions = new List<IMention>();
if (this.ReferencedMessage != null && this.MentionedUsersInternal.Contains(this.ReferencedMessage.Author))
mentions.Add(new RepliedUserMention());
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)));
return mentions;
}
/// <summary>
/// Populates the mentions.
/// </summary>
internal void PopulateMentions()
{
var guild = this.Channel?.Guild;
this.MentionedUsersInternal ??= new List<DiscordUser>();
this.MentionedRolesInternal ??= new List<DiscordRole>();
this.MentionedChannelsInternal ??= new List<DiscordChannel>();
var mentionedUsers = new HashSet<DiscordUser>(new DiscordUserComparer());
if (guild != null)
{
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.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.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.MentionedUsersInternal = mentionedUsers.ToList();
}
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Edits the message.
/// </summary>
/// <param name="content">New content.</param>
/// <exception cref="UnauthorizedException">Thrown when the client tried to modify a message not sent by them.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordMessage> ModifyAsync(Optional<string> content)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, content, default, this.GetMentions(), default, default, Array.Empty<DiscordMessageFile>(), default);
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Edits the message.
/// </summary>
/// <param name="embed">New embed.</param>
/// <exception cref="UnauthorizedException">Thrown when the client tried to modify a message not sent by them.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordMessage> ModifyAsync(Optional<DiscordEmbed> embed = default)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, default, embed.Map(v => new[] { v }).ValueOr(Array.Empty<DiscordEmbed>()), this.GetMentions(), default, default, Array.Empty<DiscordMessageFile>(), default);
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Edits the message.
/// </summary>
/// <param name="content">New content.</param>
/// <param name="embed">New embed.</param>
/// <exception cref="UnauthorizedException">Thrown when the client tried to modify a message not sent by them.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordMessage> ModifyAsync(Optional<string> content, Optional<DiscordEmbed> embed = default)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, content, embed.Map(v => new[] { v }).ValueOr(Array.Empty<DiscordEmbed>()), this.GetMentions(), default, default, Array.Empty<DiscordMessageFile>(), default);
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Edits the message.
/// </summary>
/// <param name="content">New content.</param>
/// <param name="embeds">New embeds.</param>
/// <exception cref="UnauthorizedException">Thrown when the client tried to modify a message not sent by them.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordMessage> ModifyAsync(Optional<string> content, Optional<IEnumerable<DiscordEmbed>> embeds = default)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, content, embeds, this.GetMentions(), default, default, Array.Empty<DiscordMessageFile>(), default);
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Edits the message.
/// </summary>
/// <param name="builder">The builder of the message to edit.</param>
/// <exception cref="UnauthorizedException">Thrown when the client tried to modify a message not sent by them.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordMessage> ModifyAsync(DiscordMessageBuilder builder)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
{
builder.Validate(true);
return await this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, builder.Content, Optional.Some(builder.Embeds.AsEnumerable()), builder.Mentions, builder.Components, builder.Suppressed, builder.Files, builder.Attachments.Count > 0 ? Optional.Some(builder.Attachments.AsEnumerable()) : builder.KeepAttachmentsInternal.HasValue ? builder.KeepAttachmentsInternal.Value ? Optional.Some(this.Attachments.AsEnumerable()) : Array.Empty<DiscordAttachment>() : null);
}
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Edits the message embed suppression.
/// </summary>
/// <param name="suppress">Suppress embeds.</param>
/// <exception cref="UnauthorizedException">Thrown when the client tried to modify a message not sent by them.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordMessage> ModifySuppressionAsync(bool suppress = false)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, default, default, default, default, suppress, default, default);
/// <summary>
/// Clears all attachments from the message.
/// </summary>
/// <returns></returns>
public Task<DiscordMessage> ClearAttachmentsAsync()
=> this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, default, default, this.GetMentions(), default, default, default, Array.Empty<DiscordAttachment>());
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Edits the message.
/// </summary>
/// <param name="action">The builder of the message to edit.</param>
/// <exception cref="UnauthorizedException">Thrown when the client tried to modify a message not sent by them.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordMessage> ModifyAsync(Action<DiscordMessageBuilder> action)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
{
var builder = new DiscordMessageBuilder();
action(builder);
builder.Validate(true);
return await this.Discord.ApiClient.EditMessageAsync(this.ChannelId, this.Id, builder.Content, Optional.Some(builder.Embeds.AsEnumerable()), builder.Mentions, builder.Components, builder.Suppressed, builder.Files, builder.Attachments.Count > 0 ? Optional.Some(builder.Attachments.AsEnumerable()) : builder.KeepAttachmentsInternal.HasValue ? builder.KeepAttachmentsInternal.Value ? Optional.Some(this.Attachments.AsEnumerable()) : Array.Empty<DiscordAttachment>() : null);
}
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Deletes the message.
/// </summary>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageMessages"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task DeleteAsync(string reason = null)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> this.Discord.ApiClient.DeleteMessageAsync(this.ChannelId, this.Id, reason);
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Creates a thread.
/// Depending on the <see cref="ChannelType"/> of the parent channel it's either a <see cref="ChannelType.PublicThread"/> or a <see cref="ChannelType.NewsThread"/>.
/// </summary>
/// <param name="name">The name of the thread.</param>
/// <param name="autoArchiveDuration"><see cref="ThreadAutoArchiveDuration"/> till it gets archived. Defaults to <see cref="ThreadAutoArchiveDuration.OneHour"/></param>
/// <param name="rateLimitPerUser">The per user ratelimit, aka slowdown.</param>
/// <param name="reason">The reason.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.CreatePrivateThreads"/> or <see cref="Permissions.SendMessagesInThreads"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the channel does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
/// <exception cref="NotSupportedException">Thrown when the <see cref="ThreadAutoArchiveDuration"/> cannot be modified.</exception>
public async Task<DiscordThreadChannel> CreateThreadAsync(string name, ThreadAutoArchiveDuration autoArchiveDuration = ThreadAutoArchiveDuration.OneHour, int? rateLimitPerUser = null, string reason = null) =>
Utilities.CheckThreadAutoArchiveDurationFeature(this.Channel.Guild, autoArchiveDuration)
? await this.Discord.ApiClient.CreateThreadAsync(this.ChannelId, this.Id, name, autoArchiveDuration, this.Channel.Type == ChannelType.News ? ChannelType.NewsThread : ChannelType.PublicThread, rateLimitPerUser, isForum: false, reason: reason)
: throw new NotSupportedException($"Cannot modify ThreadAutoArchiveDuration. Guild needs boost tier {(autoArchiveDuration == ThreadAutoArchiveDuration.ThreeDays ? "one" : "two")}.");
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Pins the message in its channel.
/// </summary>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageMessages"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task PinAsync()
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> this.Discord.ApiClient.PinMessageAsync(this.ChannelId, this.Id);
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Unpins the message in its channel.
/// </summary>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageMessages"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task UnpinAsync()
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> this.Discord.ApiClient.UnpinMessageAsync(this.ChannelId, this.Id);
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Responds to the message. This produces a reply.
/// </summary>
/// <param name="content">Message content to respond with.</param>
/// <returns>The sent message.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.SendMessages"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordMessage> RespondAsync(string content)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> this.Discord.ApiClient.CreateMessageAsync(this.ChannelId, content, null, sticker: null, replyMessageId: this.Id, mentionReply: false, failOnInvalidReply: false);
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Responds to the message. This produces a reply.
/// </summary>
/// <param name="embed">Embed to attach to the message.</param>
/// <returns>The sent message.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.SendMessages"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordMessage> RespondAsync(DiscordEmbed embed)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> this.Discord.ApiClient.CreateMessageAsync(this.ChannelId, null, embed != null ? new[] { embed } : null, sticker: null, replyMessageId: this.Id, mentionReply: false, failOnInvalidReply: false);
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Responds to the message. This produces a reply.
/// </summary>
/// <param name="content">Message content to respond with.</param>
/// <param name="embed">Embed to attach to the message.</param>
/// <returns>The sent message.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.SendMessages"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordMessage> RespondAsync(string content, DiscordEmbed embed)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> this.Discord.ApiClient.CreateMessageAsync(this.ChannelId, content, embed != null ? new[] { embed } : null, sticker: null, replyMessageId: this.Id, mentionReply: false, failOnInvalidReply: false);
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Responds to the message. This produces a reply.
/// </summary>
/// <param name="builder">The Discord message builder.</param>
/// <returns>The sent message.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.SendMessages"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordMessage> RespondAsync(DiscordMessageBuilder builder)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> this.Discord.ApiClient.CreateMessageAsync(this.ChannelId, builder.WithReply(this.Id, mention: false, failOnInvalidReply: false));
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Responds to the message. This produces a reply.
/// </summary>
/// <param name="action">The Discord message builder.</param>
/// <returns>The sent message.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.SendMessages"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the member does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordMessage> RespondAsync(Action<DiscordMessageBuilder> action)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
{
var builder = new DiscordMessageBuilder();
action(builder);
return this.Discord.ApiClient.CreateMessageAsync(this.ChannelId, builder.WithReply(this.Id, mention: false, failOnInvalidReply: false));
}
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Creates a reaction to this message.
/// </summary>
/// <param name="emoji">The emoji you want to react with, either an emoji or name:id</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.AddReactions"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the emoji does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task CreateReactionAsync(DiscordEmoji emoji)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> this.Discord.ApiClient.CreateReactionAsync(this.ChannelId, this.Id, emoji.ToReactionString());
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Deletes your own reaction
/// </summary>
/// <param name="emoji">Emoji for the reaction you want to remove, either an emoji or name:id</param>
/// <exception cref="NotFoundException">Thrown when the emoji does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task DeleteOwnReactionAsync(DiscordEmoji emoji)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> this.Discord.ApiClient.DeleteOwnReactionAsync(this.ChannelId, this.Id, emoji.ToReactionString());
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Deletes another user's reaction.
/// </summary>
/// <param name="emoji">Emoji for the reaction you want to remove, either an emoji or name:id.</param>
/// <param name="user">Member you want to remove the reaction for</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageMessages"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the emoji does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task DeleteReactionAsync(DiscordEmoji emoji, DiscordUser user, string reason = null)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> this.Discord.ApiClient.DeleteUserReactionAsync(this.ChannelId, this.Id, user.Id, emoji.ToReactionString(), reason);
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Gets users that reacted with this emoji.
/// </summary>
/// <param name="emoji">Emoji to react with.</param>
/// <param name="limit">Limit of users to fetch.</param>
/// <param name="after">Fetch users after this user's id.</param>
/// <exception cref="NotFoundException">Thrown when the emoji does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<IReadOnlyList<DiscordUser>> GetReactionsAsync(DiscordEmoji emoji, int limit = 25, ulong? after = null)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> this.GetReactionsInternalAsync(emoji, limit, after);
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Deletes all reactions for this message.
/// </summary>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageMessages"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the emoji does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task DeleteAllReactionsAsync(string reason = null)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> this.Discord.ApiClient.DeleteAllReactionsAsync(this.ChannelId, this.Id, reason);
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Deletes all reactions of a specific reaction for this message.
/// </summary>
/// <param name="emoji">The emoji to clear, either an emoji or name:id.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageMessages"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the emoji does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task DeleteReactionsEmojiAsync(DiscordEmoji emoji)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> this.Discord.ApiClient.DeleteReactionsEmojiAsync(this.ChannelId, this.Id, emoji.ToReactionString());
/// <summary>
/// Gets the reactions.
/// </summary>
/// <param name="emoji">The emoji to search for.</param>
/// <param name="limit">The limit of results.</param>
/// <param name="after">Get the reasctions after snowflake.</param>
private async Task<IReadOnlyList<DiscordUser>> 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<DiscordUser>();
var users = new List<DiscordUser>(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<DiscordUser>(users);
}
/// <summary>
/// Returns a string representation of this message.
/// </summary>
/// <returns>String representation of this message.</returns>
public override string ToString()
=> $"Message {this.Id}; Attachment count: {this.AttachmentsInternal.Count}; Embed count: {this.EmbedsInternal.Count}; Contents: {this.Content}";
/// <summary>
/// Checks whether this <see cref="DiscordMessage"/> is equal to another object.
/// </summary>
/// <param name="obj">Object to compare to.</param>
/// <returns>Whether the object is equal to this <see cref="DiscordMessage"/>.</returns>
public override bool Equals(object obj)
=> this.Equals(obj as DiscordMessage);
/// <summary>
/// Checks whether this <see cref="DiscordMessage"/> is equal to another <see cref="DiscordMessage"/>.
/// </summary>
/// <param name="e"><see cref="DiscordMessage"/> to compare to.</param>
/// <returns>Whether the <see cref="DiscordMessage"/> is equal to this <see cref="DiscordMessage"/>.</returns>
public bool Equals(DiscordMessage e)
=> e is not null && (ReferenceEquals(this, e) || (this.Id == e.Id && this.ChannelId == e.ChannelId));
/// <summary>
/// Gets the hash code for this <see cref="DiscordMessage"/>.
/// </summary>
/// <returns>The hash code for this <see cref="DiscordMessage"/>.</returns>
public override int GetHashCode()
{
var hash = 13;
hash = (hash * 7) + this.Id.GetHashCode();
hash = (hash * 7) + this.ChannelId.GetHashCode();
return hash;
}
/// <summary>
/// Gets whether the two <see cref="DiscordMessage"/> objects are equal.
/// </summary>
/// <param name="e1">First message to compare.</param>
/// <param name="e2">Second message to compare.</param>
/// <returns>Whether the two messages are equal.</returns>
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));
}
/// <summary>
/// Gets whether the two <see cref="DiscordMessage"/> objects are not equal.
/// </summary>
/// <param name="e1">First message to compare.</param>
/// <param name="e2">Second message to compare.</param>
/// <returns>Whether the two messages are not equal.</returns>
public static bool operator !=(DiscordMessage e1, DiscordMessage e2)
=> !(e1 == e2);
}
diff --git a/DisCatSharp/Entities/Message/DiscordMessageActivity.cs b/DisCatSharp/Entities/Message/DiscordMessageActivity.cs
index 8381153ac..cca84c395 100644
--- a/DisCatSharp/Entities/Message/DiscordMessageActivity.cs
+++ b/DisCatSharp/Entities/Message/DiscordMessageActivity.cs
@@ -1,51 +1,51 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a Rich Presence activity.
/// </summary>
public class DiscordMessageActivity
{
/// <summary>
/// Gets the activity type.
/// </summary>
[JsonProperty("type")]
public MessageActivityType Type { get; internal set; }
/// <summary>
/// Gets the party id of the activity.
/// </summary>
[JsonProperty("party_id")]
public string PartyId { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordMessageActivity"/> class.
/// </summary>
internal DiscordMessageActivity()
{ }
}
diff --git a/DisCatSharp/Entities/Message/DiscordMessageApplication.cs b/DisCatSharp/Entities/Message/DiscordMessageApplication.cs
index 2f6cfa2cc..22e0ab4b8 100644
--- a/DisCatSharp/Entities/Message/DiscordMessageApplication.cs
+++ b/DisCatSharp/Entities/Message/DiscordMessageApplication.cs
@@ -1,61 +1,61 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a Rich Presence application.
/// </summary>
public class DiscordMessageApplication : SnowflakeObject
{
/// <summary>
/// Gets the ID of this application's cover image.
/// </summary>
[JsonProperty("cover_image")]
public virtual string CoverImageUrl { get; internal set; }
/// <summary>
/// Gets the application's description.
/// </summary>
[JsonProperty("description")]
public string Description { get; internal set; }
/// <summary>
/// Gets the ID of the application's icon.
/// </summary>
[JsonProperty("icon")]
public virtual string Icon { get; internal set; }
/// <summary>
/// Gets the application's name.
/// </summary>
[JsonProperty("name")]
public string Name { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordMessageApplication"/> class.
/// </summary>
internal DiscordMessageApplication()
{ }
}
diff --git a/DisCatSharp/Entities/Message/DiscordMessageBuilder.cs b/DisCatSharp/Entities/Message/DiscordMessageBuilder.cs
index 8de34e824..158eaf3dd 100644
--- a/DisCatSharp/Entities/Message/DiscordMessageBuilder.cs
+++ b/DisCatSharp/Entities/Message/DiscordMessageBuilder.cs
@@ -1,459 +1,459 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Constructs a Message to be sent.
/// </summary>
public sealed class DiscordMessageBuilder
{
/// <summary>
/// Gets or Sets the Message to be sent.
/// </summary>
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;
/// <summary>
/// Gets or sets the embed for the builder. This will always set the builder to have one embed.
/// </summary>
public DiscordEmbed Embed
{
get => this._embeds.Count > 0 ? this._embeds[0] : null;
set
{
this._embeds.Clear();
this._embeds.Add(value);
}
}
/// <summary>
/// Gets the Sticker to be send.
/// </summary>
public DiscordSticker Sticker { get; set; }
/// <summary>
/// Gets the Embeds to be sent.
/// </summary>
public IReadOnlyList<DiscordEmbed> Embeds => this._embeds;
private readonly List<DiscordEmbed> _embeds = new();
/// <summary>
/// Gets or Sets if the message should be TTS.
/// </summary>
public bool IsTts { get; set; }
/// <summary>
/// Whether to keep previous attachments.
/// </summary>
internal bool? KeepAttachmentsInternal;
/// <summary>
/// Gets the Allowed Mentions for the message to be sent.
/// </summary>
public List<IMention> Mentions { get; private set; }
/// <summary>
/// Gets the Files to be sent in the Message.
/// </summary>
public IReadOnlyCollection<DiscordMessageFile> Files => this.FilesInternal;
internal readonly List<DiscordMessageFile> FilesInternal = new();
/// <summary>
/// Gets the components that will be attached to the message.
/// </summary>
public IReadOnlyList<DiscordActionRowComponent> Components => this.ComponentsInternal;
internal readonly List<DiscordActionRowComponent> ComponentsInternal = new(5);
/// <summary>
/// Gets the Attachments to be sent in the Message.
/// </summary>
public IReadOnlyList<DiscordAttachment> Attachments => this.AttachmentsInternal;
internal readonly List<DiscordAttachment> AttachmentsInternal = new();
/// <summary>
/// Gets the Reply Message ID.
/// </summary>
public ulong? ReplyId { get; private set; }
/// <summary>
/// Gets if the Reply should mention the user.
/// </summary>
public bool MentionOnReply { get; private set; }
/// <summary>
/// Gets if the embeds should be suppressed.
/// </summary>
public bool Suppressed { get; private set; }
/// <summary>
/// Gets if the Reply will error if the Reply Message Id does not reference a valid message.
/// <para>If set to false, invalid replies are send as a regular message.</para>
/// <para>Defaults to false.</para>
/// </summary>
public bool FailOnInvalidReply { get; set; }
/// <summary>
/// Sets the Content of the Message.
/// </summary>
/// <param name="content">The content to be set.</param>
/// <returns>The current builder to be chained.</returns>
public DiscordMessageBuilder WithContent(string content)
{
this.Content = content;
return this;
}
/// <summary>
/// Adds a sticker to the message. Sticker must be from current guild.
/// </summary>
/// <param name="sticker">The sticker to add.</param>
/// <returns>The current builder to be chained.</returns>
public DiscordMessageBuilder WithSticker(DiscordSticker sticker)
{
this.Sticker = sticker;
return this;
}
/// <summary>
/// Adds a row of components to a message, up to 5 components per row, and up to 5 rows per message.
/// </summary>
/// <param name="components">The components to add to the message.</param>
/// <returns>The current builder to be chained.</returns>
/// <exception cref="ArgumentOutOfRangeException">No components were passed.</exception>
public DiscordMessageBuilder AddComponents(params DiscordComponent[] components)
=> this.AddComponents((IEnumerable<DiscordComponent>)components);
/// <summary>
/// Appends several rows of components to the message
/// </summary>
/// <param name="components">The rows of components to add, holding up to five each.</param>
/// <returns></returns>
public DiscordMessageBuilder AddComponents(IEnumerable<DiscordActionRowComponent> components)
{
var ara = components.ToArray();
if (ara.Length + this.ComponentsInternal.Count > 5)
throw new ArgumentException("ActionRow count exceeds maximum of five.");
foreach (var ar in ara)
this.ComponentsInternal.Add(ar);
return this;
}
/// <summary>
/// Adds a row of components to a message, up to 5 components per row, and up to 5 rows per message.
/// </summary>
/// <param name="components">The components to add to the message.</param>
/// <returns>The current builder to be chained.</returns>
/// <exception cref="ArgumentOutOfRangeException">No components were passed.</exception>
public DiscordMessageBuilder AddComponents(IEnumerable<DiscordComponent> 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.ComponentsInternal.Add(comp);
return this;
}
/// <summary>
/// Sets if the message should be TTS.
/// </summary>
/// <param name="isTts">If TTS should be set.</param>
/// <returns>The current builder to be chained.</returns>
public DiscordMessageBuilder HasTts(bool isTts)
{
this.IsTts = isTts;
return this;
}
/// <summary>
/// Sets the embed for the current builder.
/// </summary>
/// <param name="embed">The embed that should be set.</param>
/// <returns>The current builder to be chained.</returns>
public DiscordMessageBuilder WithEmbed(DiscordEmbed embed)
{
if (embed == null)
return this;
this.Embed = embed;
return this;
}
/// <summary>
/// Appends an embed to the current builder.
/// </summary>
/// <param name="embed">The embed that should be appended.</param>
/// <returns>The current builder to be chained.</returns>
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;
}
/// <summary>
/// Appends several embeds to the current builder.
/// </summary>
/// <param name="embeds">The embeds that should be appended.</param>
/// <returns>The current builder to be chained.</returns>
public DiscordMessageBuilder AddEmbeds(IEnumerable<DiscordEmbed> embeds)
{
this._embeds.AddRange(embeds);
return this;
}
/// <summary>
/// Sets if the message has allowed mentions.
/// </summary>
/// <param name="allowedMention">The allowed Mention that should be sent.</param>
/// <returns>The current builder to be chained.</returns>
public DiscordMessageBuilder WithAllowedMention(IMention allowedMention)
{
if (this.Mentions != null)
this.Mentions.Add(allowedMention);
else
this.Mentions = new List<IMention> { allowedMention };
return this;
}
/// <summary>
/// Sets if the message has allowed mentions.
/// </summary>
/// <param name="allowedMentions">The allowed Mentions that should be sent.</param>
/// <returns>The current builder to be chained.</returns>
public DiscordMessageBuilder WithAllowedMentions(IEnumerable<IMention> allowedMentions)
{
if (this.Mentions != null)
this.Mentions.AddRange(allowedMentions);
else
this.Mentions = allowedMentions.ToList();
return this;
}
/// <summary>
/// Sets if the message has files to be sent.
/// </summary>
/// <param name="fileName">The fileName that the file should be sent as.</param>
/// <param name="stream">The Stream to the file.</param>
/// <param name="resetStreamPosition">Tells the API Client to reset the stream position to what it was after the file is sent.</param>
/// <param name="description">Description of the file.</param>
/// <returns>The current builder to be chained.</returns>
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.FilesInternal.Any(x => x.FileName == fileName))
throw new ArgumentException("A File with that filename already exists");
if (resetStreamPosition)
this.FilesInternal.Add(new DiscordMessageFile(fileName, stream, stream.Position, description: description));
else
this.FilesInternal.Add(new DiscordMessageFile(fileName, stream, null, description: description));
return this;
}
/// <summary>
/// Sets if the message has files to be sent.
/// </summary>
/// <param name="stream">The Stream to the file.</param>
/// <param name="resetStreamPosition">Tells the API Client to reset the stream position to what it was after the file is sent.</param>
/// <param name="description">Description of the file.</param>
/// <returns>The current builder to be chained.</returns>
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.FilesInternal.Any(x => x.FileName == stream.Name))
throw new ArgumentException("A File with that filename already exists");
if (resetStreamPosition)
this.FilesInternal.Add(new DiscordMessageFile(stream.Name, stream, stream.Position, description: description));
else
this.FilesInternal.Add(new DiscordMessageFile(stream.Name, stream, null, description: description));
return this;
}
/// <summary>
/// Sets if the message has files to be sent.
/// </summary>
/// <param name="files">The Files that should be sent.</param>
/// <param name="resetStreamPosition">Tells the API Client to reset the stream position to what it was after the file is sent.</param>
/// <returns>The current builder to be chained.</returns>
public DiscordMessageBuilder WithFiles(Dictionary<string, Stream> 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.FilesInternal.Any(x => x.FileName == file.Key))
throw new ArgumentException("A File with that filename already exists");
if (resetStreamPosition)
this.FilesInternal.Add(new DiscordMessageFile(file.Key, file.Value, file.Value.Position));
else
this.FilesInternal.Add(new DiscordMessageFile(file.Key, file.Value, null));
}
return this;
}
/// <summary>
/// Modifies the given attachments on edit.
/// </summary>
/// <param name="attachments">Attachments to edit.</param>
/// <returns></returns>
public DiscordMessageBuilder ModifyAttachments(IEnumerable<DiscordAttachment> attachments)
{
this.AttachmentsInternal.AddRange(attachments);
return this;
}
/// <summary>
/// Whether to keep the message attachments, if new ones are added.
/// </summary>
/// <returns></returns>
public DiscordMessageBuilder KeepAttachments(bool keep)
{
this.KeepAttachmentsInternal = keep;
return this;
}
/// <summary>
/// Sets if the message is a reply
/// </summary>
/// <param name="messageId">The ID of the message to reply to.</param>
/// <param name="mention">If we should mention the user in the reply.</param>
/// <param name="failOnInvalidReply">Whether sending a reply that references an invalid message should be </param>
/// <returns>The current builder to be chained.</returns>
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<IMention>();
this.Mentions.Add(new RepliedUserMention());
}
return this;
}
/// <summary>
/// Sends the Message to a specific channel
/// </summary>
/// <param name="channel">The channel the message should be sent to.</param>
/// <returns>The current builder to be chained.</returns>
public Task<DiscordMessage> SendAsync(DiscordChannel channel) => channel.SendMessageAsync(this);
/// <summary>
/// Sends the modified message.
/// <para>Note: Message replies cannot be modified. To clear the reply, simply pass <see langword="null"/> to <see cref="WithReply"/>.</para>
/// </summary>
/// <param name="msg">The original Message to modify.</param>
/// <returns>The current builder to be chained.</returns>
public Task<DiscordMessage> ModifyAsync(DiscordMessage msg) => msg.ModifyAsync(this);
/// <summary>
/// Clears all message components on this builder.
/// </summary>
public void ClearComponents()
=> this.ComponentsInternal.Clear();
/// <summary>
/// Allows for clearing the Message Builder so that it can be used again to send a new message.
/// </summary>
public void Clear()
{
this.Content = "";
this._embeds.Clear();
this.IsTts = false;
this.Mentions = null;
this.FilesInternal.Clear();
this.ReplyId = null;
this.MentionOnReply = false;
this.ComponentsInternal.Clear();
this.Suppressed = false;
this.Sticker = null;
this.AttachmentsInternal.Clear();
this.KeepAttachmentsInternal = false;
}
/// <summary>
/// Does the validation before we send a the Create/Modify request.
/// </summary>
/// <param name="isModify">Tells the method to perform the Modify Validation or Create Validation.</param>
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 && (!this.Embeds?.Any() ?? true))
throw new ArgumentException("You must specify content, an embed, a sticker, a component 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/Message/DiscordMessageFile.cs b/DisCatSharp/Entities/Message/DiscordMessageFile.cs
index 689ed18a8..46a2b4ac5 100644
--- a/DisCatSharp/Entities/Message/DiscordMessageFile.cs
+++ b/DisCatSharp/Entities/Message/DiscordMessageFile.cs
@@ -1,80 +1,80 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.IO;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents the File that should be sent to Discord from the <see cref="DisCatSharp.Entities.DiscordMessageBuilder"/>.
/// </summary>
public class DiscordMessageFile
{
/// <summary>
/// Initializes a new instance of the <see cref="DiscordMessageFile"/> class.
/// </summary>
/// <param name="fileName">The file name.</param>
/// <param name="stream">The stream.</param>
/// <param name="resetPositionTo">The reset position to.</param>
/// <param name="fileType">The file type.</param>
/// <param name="contentType">The content type.</param>
/// <param name="description">The description.</param>
internal DiscordMessageFile(string fileName, Stream stream, long? resetPositionTo, string fileType = null, string contentType = null, string description = null)
{
this.FileName = fileName;
this.FileType = fileType;
this.ContentType = contentType;
this.Stream = stream;
this.ResetPositionTo = resetPositionTo;
this.Description = description;
}
/// <summary>
/// Gets the FileName of the File.
/// </summary>
public string FileName { get; internal set; }
/// <summary>
/// Gets the description of the File.
/// </summary>
public string Description { get; internal set; }
/// <summary>
/// Gets the stream of the File.
/// </summary>
public Stream Stream { get; internal set; }
/// <summary>
/// Gets or sets the file type.
/// </summary>
internal string FileType { get; set; }
/// <summary>
/// Gets or sets the content type.
/// </summary>
internal string ContentType { get; set; }
/// <summary>
/// Gets the position the File should be reset to.
/// </summary>
internal long? ResetPositionTo { get; set; }
}
diff --git a/DisCatSharp/Entities/Message/DiscordMessageInteraction.cs b/DisCatSharp/Entities/Message/DiscordMessageInteraction.cs
index 79f0cb214..85a26ba56 100644
--- a/DisCatSharp/Entities/Message/DiscordMessageInteraction.cs
+++ b/DisCatSharp/Entities/Message/DiscordMessageInteraction.cs
@@ -1,51 +1,51 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents the message interaction data sent when a message is an interaction response.
/// </summary>
public class DiscordMessageInteraction : SnowflakeObject
{
/// <summary>
/// Gets the type of the interaction.
/// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
public InteractionType Type { get; internal set; }
/// <summary>
/// Gets the name of the <see cref="DiscordApplicationCommand"/>.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; internal set; }
/// <summary>
/// Gets the user who invoked the interaction.
/// </summary>
[JsonProperty("user", NullValueHandling = NullValueHandling.Ignore)]
public DiscordUser User { get; internal set; }
}
diff --git a/DisCatSharp/Entities/Message/DiscordMessageReference.cs b/DisCatSharp/Entities/Message/DiscordMessageReference.cs
index c339fb249..73fb7d316 100644
--- a/DisCatSharp/Entities/Message/DiscordMessageReference.cs
+++ b/DisCatSharp/Entities/Message/DiscordMessageReference.cs
@@ -1,84 +1,84 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents data from the original message.
/// </summary>
public class DiscordMessageReference
{
/// <summary>
/// Gets the original message.
/// </summary>
public DiscordMessage Message { get; internal set; }
/// <summary>
/// Gets the channel of the original message.
/// </summary>
public DiscordChannel Channel { get; internal set; }
/// <summary>
/// Gets the guild of the original message.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets a readable message reference string.
/// </summary>
public override string ToString()
=> $"Guild: {this.Guild.Id}, Channel: {this.Channel.Id}, Message: {this.Message.Id}";
/// <summary>
/// Initializes a new instance of the <see cref="DiscordMessageReference"/> class.
/// </summary>
internal DiscordMessageReference() { }
}
internal struct InternalDiscordMessageReference
{
/// <summary>
/// Gets the message id.
/// </summary>
[JsonProperty("message_id", NullValueHandling = NullValueHandling.Ignore)]
internal ulong? MessageId { get; set; }
/// <summary>
/// Gets the channel id.
/// </summary>
[JsonProperty("channel_id", NullValueHandling = NullValueHandling.Ignore)]
internal ulong? ChannelId { get; set; }
/// <summary>
/// Gets the guild id.
/// </summary>
[JsonProperty("guild_id", NullValueHandling = NullValueHandling.Ignore)]
internal ulong? GuildId { get; set; }
/// <summary>
/// Whether it should fail if it does not exists.
/// </summary>
[JsonProperty("fail_if_not_exists", NullValueHandling = NullValueHandling.Ignore)]
public bool FailIfNotExists { get; set; }
}
diff --git a/DisCatSharp/Entities/Message/DiscordReaction.cs b/DisCatSharp/Entities/Message/DiscordReaction.cs
index c7fd388d1..a7c348cc1 100644
--- a/DisCatSharp/Entities/Message/DiscordReaction.cs
+++ b/DisCatSharp/Entities/Message/DiscordReaction.cs
@@ -1,79 +1,79 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a reaction to a message.
/// </summary>
public class DiscordReaction
{
/// <summary>
/// Gets the total number of users who reacted with this emoji.
/// </summary>
[JsonProperty("count", NullValueHandling = NullValueHandling.Ignore)]
public int Count { get; internal set; }
/// <summary>
/// Gets the total number of users who burst reacted with this emoji.
/// </summary>
[JsonProperty("burst_count", NullValueHandling = NullValueHandling.Ignore)]
public int BurstCount { get; internal set; }
/// <summary>
/// Gets whether the current user burst reacted with this emoji.
/// </summary>
[JsonProperty("burst_me", NullValueHandling = NullValueHandling.Ignore)]
public bool BurstMe { get; internal set; }
/// <summary>
/// Gets the ids of users who burst reacted with this emoji.
/// </summary>
[JsonProperty("burst_user_ids", NullValueHandling = NullValueHandling.Ignore)]
public ulong[] BurstUserIds { get; internal set; }
/// <summary>
/// Gets the burst colors.
/// </summary>
[JsonProperty("burst_colors", NullValueHandling = NullValueHandling.Ignore)]
public string[] BurstColors { get; internal set; }
/// <summary>
/// Gets whether the current user reacted with this emoji.
/// </summary>
[JsonProperty("me", NullValueHandling = NullValueHandling.Ignore)]
public bool IsMe { get; internal set; }
/// <summary>
/// Gets the emoji used to react to this message.
/// </summary>
[JsonProperty("emoji", NullValueHandling = NullValueHandling.Ignore)]
public DiscordEmoji Emoji { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordReaction"/> class.
/// </summary>
internal DiscordReaction()
{ }
}
diff --git a/DisCatSharp/Entities/Message/Mentions.cs b/DisCatSharp/Entities/Message/Mentions.cs
index 5c3638a58..a793ffc22 100644
--- a/DisCatSharp/Entities/Message/Mentions.cs
+++ b/DisCatSharp/Entities/Message/Mentions.cs
@@ -1,132 +1,132 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Interface for mentionables
/// </summary>
public interface IMention { }
/// <summary>
/// Allows a reply to ping the user being replied to.
/// </summary>
public readonly struct RepliedUserMention : IMention
{
//This is pointless because new RepliedUserMention() will work, but it is here for consistency with the other mentionables.
/// <summary>
/// Mention the user being replied to. Alias to <see cref="RepliedUserMention()"/> constructor.
/// </summary>
public static readonly RepliedUserMention All = new();
}
/// <summary>
/// Allows @everyone and @here pings to mention in the message.
/// </summary>
public readonly struct EveryoneMention : IMention
{
//This is pointless because new EveryoneMention() will work, but it is here for consistency with the other mentionables.
/// <summary>
/// Allow the mentioning of @everyone and @here. Alias to <see cref="EveryoneMention()"/> constructor.
/// </summary>
public static readonly EveryoneMention All = new();
}
/// <summary>
/// Allows @user pings to mention in the message.
/// </summary>
public readonly struct UserMention : IMention
{
/// <summary>
/// Allow mentioning of all users. Alias to <see cref="UserMention()"/> constructor.
/// </summary>
public static readonly UserMention All = new();
/// <summary>
/// Optional Id of the user that is allowed to be mentioned. If null, then all user mentions will be allowed.
/// </summary>
public ulong? Id { get; }
/// <summary>
/// Allows the specific user to be mentioned
/// </summary>
/// <param name="id"></param>
public UserMention(ulong id) { this.Id = id; }
/// <summary>
/// Allows the specific user to be mentioned
/// </summary>
/// <param name="user"></param>
public UserMention(DiscordUser user) : this(user.Id) { }
public static implicit operator UserMention(DiscordUser user) => new(user.Id);
}
/// <summary>
/// Allows @role pings to mention in the message.
/// </summary>
public readonly struct RoleMention : IMention
{
/// <summary>
/// Allow the mentioning of all roles. Alias to <see cref="RoleMention()"/> constructor.
/// </summary>
public static readonly RoleMention All = new();
/// <summary>
/// Optional Id of the role that is allowed to be mentioned. If null, then all role mentions will be allowed.
/// </summary>
public ulong? Id { get; }
/// <summary>
/// Allows the specific id to be mentioned
/// </summary>
/// <param name="id"></param>
public RoleMention(ulong id) { this.Id = id; }
/// <summary>
/// Allows the specific role to be mentioned
/// </summary>
/// <param name="role"></param>
public RoleMention(DiscordRole role) : this(role.Id) { }
public static implicit operator RoleMention(DiscordRole role) => new(role.Id);
}
/// <summary>
/// Contains static instances of common mention patterns.
/// </summary>
public static class Mentions
{
/// <summary>
/// All possible mentions - @everyone + @here, users, and roles.
/// </summary>
public static IReadOnlyList<IMention> All { get; } = new IMention[] { EveryoneMention.All, UserMention.All, RoleMention.All };
/// <summary>
/// No mentions allowed.
/// </summary>
public static IReadOnlyList<IMention> None { get; } = Array.Empty<IMention>();
}
diff --git a/DisCatSharp/Entities/NullableSnowflakeObject.cs b/DisCatSharp/Entities/NullableSnowflakeObject.cs
index b06dfb006..3562fc1b0 100644
--- a/DisCatSharp/Entities/NullableSnowflakeObject.cs
+++ b/DisCatSharp/Entities/NullableSnowflakeObject.cs
@@ -1,57 +1,57 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Entities;
/// <summary>
/// Represents an object in Discord API.
/// </summary>
public abstract class NullableSnowflakeObject
{
/// <summary>
/// Gets the ID of this object.
/// </summary>
[JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? Id { get; internal set; }
/// <summary>
/// Gets the date and time this object was created.
/// </summary>
[JsonIgnore]
public DateTimeOffset? CreationTimestamp
=> this.Id.GetSnowflakeTime();
/// <summary>
/// Gets the client instance this object is tied to.
/// </summary>
[JsonIgnore]
internal BaseDiscordClient Discord { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="NullableSnowflakeObject"/> class.
/// </summary>
internal NullableSnowflakeObject() { }
}
diff --git a/DisCatSharp/Entities/Optional.cs b/DisCatSharp/Entities/Optional.cs
index 2809e2d8f..7d9e52fba 100644
--- a/DisCatSharp/Entities/Optional.cs
+++ b/DisCatSharp/Entities/Optional.cs
@@ -1,400 +1,400 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
using System.Reflection;
using DisCatSharp.Net.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
namespace DisCatSharp.Entities;
/// <summary>
/// Helper methods for instantiating an <see cref="Optional{T}"/>.
/// </summary>
/// <remarks>
/// This class only serves to provide <see cref="Some{T}"/> and <see cref="None"/>
/// as utility that supports type inference.
/// </remarks>
public static class Optional
{
/// <summary>
/// Provided for easy creation of empty <see cref="Optional{T}"/>s.
/// </summary>
public static readonly None None = new();
/// <summary>
/// Creates a new <see cref="Optional{T}"/> with specified value and valid state.
/// </summary>
/// <param name="value">Value to populate the optional with.</param>
/// <typeparam name="T">Type of the value.</typeparam>
/// <returns>Created optional.</returns>
public static Optional<T> Some<T>(T value)
=> value;
/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <returns></returns>
public static Optional<T> FromNullable<T>(T value)
=> value == null
? None
: value;
/// <summary>
/// Creates a new <see cref="Optional{T}"/> with specified value and valid state.
/// </summary>
/// <param name="value">Value to populate the optional with.</param>
/// <typeparam name="T">Type of the value.</typeparam>
/// <returns>Created optional.</returns>
[Obsolete("Renamed to Some.")]
public static Optional<T> FromValue<T>(T value)
=> value;
/// <summary>
/// Creates a new empty <see cref="Optional{T}"/> with no value and invalid state.
/// </summary>
/// <typeparam name="T">The type that the created instance is wrapping around.</typeparam>
/// <returns>Created optional.</returns>
[Obsolete("Use None.")]
public static Optional<T> FromNoValue<T>()
=> default;
}
/// <summary>
/// Unit type for creating an empty <see cref="Optional{T}"/>s.
/// </summary>
public struct None
{ }
/// <summary>
/// Used internally to make serialization more convenient, do NOT change this, do NOT implement this yourself.
/// </summary>
internal interface IOptional
{
/// <summary>
/// Gets a whether it has a value.
/// </summary>
bool HasValue { get; }
/// <summary>
/// Gets the raw value.
/// </summary>
/// <remarks>
/// Must NOT throw InvalidOperationException.
/// </remarks>
object RawValue { get; }
}
/// <summary>
/// Represents a wrapper which may or may not have a value.
/// </summary>
/// <typeparam name="T">Type of the value.</typeparam>
[JsonConverter(typeof(OptionalJsonConverter))]
public readonly struct Optional<T> : IEquatable<Optional<T>>, IEquatable<T>, IOptional
{
/// <summary>
/// Static empty <see cref="Optional"/>.
/// </summary>
public static readonly Optional<T> None = default;
/// <summary>
/// Gets whether this <see cref="Optional{T}"/> has a value.
/// </summary>
public bool HasValue { get; }
/// <summary>
/// Gets the value of this <see cref="Optional{T}"/>.
/// </summary>
/// <exception cref="InvalidOperationException">If this <see cref="Optional{T}"/> has no value.</exception>
public T Value => this.HasValue ? this._val : throw new InvalidOperationException("Value is not set.");
/// <summary>
/// Gets the raw value.
/// </summary>
object IOptional.RawValue => this._val;
private readonly T _val;
/// <summary>
/// Creates a new <see cref="Optional{T}"/> with specified value.
/// </summary>
/// <param name="value">Value of this option.</param>
[Obsolete("Use Optional.Some")]
public Optional(T value)
{
this._val = value;
this.HasValue = true;
}
[Obsolete("Renamed to Map")]
public Optional<TOut> IfPresent<TOut>(Func<T, TOut> mapper)
=> this.Map(mapper);
/// <summary>
/// Performs a mapping operation on the current <see cref="Optional{T}"/>, turning it into an Optional holding a
/// <typeparamref name="TOut"/> instance if the source optional contains a value; otherwise, returns an
/// <see cref="Optional{T}"/> of that same type with no value.
/// </summary>
/// <param name="mapper">The mapping function to apply on the current value if it exists</param>
/// <typeparam name="TOut">The type of the target value returned by <paramref name="mapper"/></typeparam>
/// <returns>
/// An <see cref="Optional{T}"/> containing a value denoted by calling <paramref name="mapper"/> if the current
/// <see cref="Optional{T}"/> contains a value; otherwise, an empty <see cref="Optional{T}"/> of the target
/// type.
/// </returns>
public Optional<TOut> Map<TOut>(Func<T, TOut> mapper)
=> this.HasValue
? mapper(this._val)
: Optional.None;
/// <summary>
/// Maps to <see cref="None"/> for <see cref="None"/>, to <code>default</code> for <code>null</code> and to the mapped value otherwise./>
/// </summary>
/// <typeparam name="TOut">The type to map to.</typeparam>
/// <param name="mapper">The function that does the mapping of the non-null <typeparamref name="T"/>.</param>
/// <returns>The mapped value.</returns>
public Optional<TOut> MapOrNull<TOut>(Func<T, TOut> mapper)
=> this.HasValue
? this._val == null
? default
: mapper(this._val)
: Optional.None;
/// <summary>
/// Gets the value of the <see cref="Optional{T}"/> or a specified value, if the <see cref="Optional{T}"/> has no value.
/// </summary>
/// <param name="other">The value to return if this has no value.</param>
/// <returns>Either the value of the <see cref="Optional{T}"/> if present or the provided value.</returns>
public T ValueOr(T other)
=> this.HasValue
? this._val
: other;
/// <summary>
/// Gets the value of the <see cref="Optional{T}"/> or the default value for <typeparamref name="T"/>, if the
/// <see cref="Optional{T}"/> has no value.
/// </summary>
/// <returns>Either the value of the <see cref="Optional{T}"/> if present or the type's default value.</returns>
public T ValueOrDefault()
=> this.ValueOr(default);
/// <summary>
/// Gets the <see cref="Optional"/>'s value, or throws the provided exception if it's empty.
/// </summary>
/// <param name="err">The exception to throw if the optional is empty.</param>
/// <returns>The value of the <see cref="Optional"/>, if present.</returns>
public T Expect(Exception err)
=> !this.HasValue ? throw err : this._val;
/// <summary>
/// Gets the <see cref="Optional"/>'s value, or throws a standard exception with the provided string if it's
/// empty.
/// </summary>
/// <param name="str">The string provided to the exception.</param>
/// <returns>The value of the <see cref="Optional"/>, if present.</returns>
public T Expect(string str) => this.Expect(new InvalidOperationException(str));
/// <summary>
/// Checks if this has a value and tests the predicate if it does.
/// </summary>
/// <param name="predicate">The predicate to test if this has a value.</param>
/// <returns>True if this has a value and the predicate is fulfilled, false otherwise.</returns>
public bool HasValueAnd(Predicate<T> predicate)
=> this.HasValue && predicate(this._val);
/// <summary>
/// Returns a string representation of this optional value.
/// </summary>
/// <returns>String representation of this optional value.</returns>
public override string ToString() => $"Optional<{typeof(T)}> ({this.Map(x => x.ToString()).ValueOr("<no value>")})";
/// <summary>
/// Checks whether this <see cref="Optional{T}"/> (or its value) are equal to another object.
/// </summary>
/// <param name="obj">Object to compare to.</param>
/// <returns>Whether the object is equal to this <see cref="Optional{T}"/> or its value.</returns>
public override bool Equals(object obj) =>
obj switch
{
T t => this.Equals(t),
Optional<T> opt => this.Equals(opt),
_ => false,
};
/// <summary>
/// Checks whether this <see cref="Optional{T}"/> is equal to another <see cref="Optional{T}"/>.
/// </summary>
/// <param name="e"><see cref="Optional{T}"/> to compare to.</param>
/// <returns>Whether the <see cref="Optional{T}"/> is equal to this <see cref="Optional{T}"/>.</returns>
public bool Equals(Optional<T> e) => (!this.HasValue && !e.HasValue) || (this.HasValue == e.HasValue && this.Value.Equals(e.Value));
/// <summary>
/// Checks whether the value of this <see cref="Optional{T}"/> is equal to specified object.
/// </summary>
/// <param name="e">Object to compare to.</param>
/// <returns>Whether the object is equal to the value of this <see cref="Optional{T}"/>.</returns>
public bool Equals(T e)
=> this.HasValue && ReferenceEquals(this.Value, e);
/// <summary>
/// Gets the hash code for this <see cref="Optional{T}"/>.
/// </summary>
/// <returns>The hash code for this <see cref="Optional{T}"/>.</returns>
public override int GetHashCode()
=> this.Map(x => x.GetHashCode()).ValueOrDefault();
public static implicit operator Optional<T>(T val)
#pragma warning disable 0618
=> new(val);
#pragma warning restore 0618
public static explicit operator T(Optional<T> opt)
=> opt.Value;
/// <summary>
/// Creates an empty optional.
/// </summary>
public static implicit operator Optional<T>(None _) => default;
public static bool operator ==(Optional<T> opt1, Optional<T> opt2)
=> opt1.Equals(opt2);
public static bool operator !=(Optional<T> opt1, Optional<T> opt2)
=> !opt1.Equals(opt2);
public static bool operator ==(Optional<T> opt, T t)
=> opt.Equals(t);
public static bool operator !=(Optional<T> opt, T t)
=> !opt.Equals(t);
}
/// <summary>
/// Represents an optional json contract resolver.
/// <seealso cref="DiscordJson.s_serializer"/>
/// </summary>
internal sealed class OptionalJsonContractResolver : DefaultContractResolver
{
/// <summary>
/// Creates the property.
/// </summary>
/// <param name="member">The member.</param>
/// <param name="memberSerialization">The member serialization.</param>
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
var type = property.PropertyType;
if (!type.GetTypeInfo().ImplementedInterfaces.Contains(typeof(IOptional)))
return property;
// we cache the PropertyInfo object here (it's captured in closure). we don't have direct
// access to the property value so we have to reflect into it from the parent instance
// we use UnderlyingName instead of PropertyName in case the C# name is different from the Json name.
var declaringMember = property.DeclaringType.GetTypeInfo().DeclaredMembers
.FirstOrDefault(e => e.Name == property.UnderlyingName);
switch (declaringMember)
{
case PropertyInfo declaringProp:
property.ShouldSerialize = instance => // instance here is the declaring (parent) type
{
var optionalValue = declaringProp.GetValue(instance);
return (optionalValue as IOptional).HasValue;
};
return property;
case FieldInfo declaringField:
property.ShouldSerialize = instance => // instance here is the declaring (parent) type
{
var optionalValue = declaringField.GetValue(instance);
return (optionalValue as IOptional).HasValue;
};
return property;
default:
throw new InvalidOperationException(
"Can only serialize Optional<T> members that are fields or properties");
}
}
}
/// <summary>
/// Represents an optional json converter.
/// </summary>
internal sealed class OptionalJsonConverter : JsonConverter
{
/// <summary>
/// Writes the json.
/// </summary>
/// <param name="writer">The writer.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The serializer.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// we don't check for HasValue here since it's checked in OptionalJsonContractResolver
var val = (value as IOptional).RawValue;
// JToken.FromObject will throw if `null` so we manually write a null value.
if (val == null)
{
// you can read serializer.NullValueHandling here, but unfortunately you can **not** skip serialization
// here, or else you will get a nasty JsonWriterException, so we just ignore its value and manually
// write the null.
writer.WriteToken(JsonToken.Null);
}
else
{
// convert the value to a JSON object and write it to the property value.
JToken.FromObject(val).WriteTo(writer);
}
}
/// <summary>
/// Reads the json.
/// </summary>
/// <param name="reader">The reader.</param>
/// <param name="objectType">The object type.</param>
/// <param name="existingValue">The existing value.</param>
/// <param name="serializer">The serializer.</param>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
var genericType = objectType.GenericTypeArguments[0];
var constructor = objectType.GetTypeInfo().DeclaredConstructors
.FirstOrDefault(e => e.GetParameters()[0].ParameterType == genericType);
return constructor.Invoke(new[] { serializer.Deserialize(reader, genericType) });
}
/// <summary>
/// Whether it can convert.
/// </summary>
/// <param name="objectType">The object type.</param>
public override bool CanConvert(Type objectType) => objectType.GetTypeInfo().ImplementedInterfaces.Contains(typeof(IOptional));
}
diff --git a/DisCatSharp/Entities/SnowflakeObject.cs b/DisCatSharp/Entities/SnowflakeObject.cs
index 44a1ab6d4..a2b867180 100644
--- a/DisCatSharp/Entities/SnowflakeObject.cs
+++ b/DisCatSharp/Entities/SnowflakeObject.cs
@@ -1,57 +1,57 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Entities;
/// <summary>
/// Represents an object in Discord API.
/// </summary>
public abstract class SnowflakeObject
{
/// <summary>
/// Gets the ID of this object.
/// </summary>
[JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)]
public ulong Id { get; internal set; }
/// <summary>
/// Gets the date and time this object was created.
/// </summary>
[JsonIgnore]
public DateTimeOffset CreationTimestamp
=> this.Id.GetSnowflakeTime();
/// <summary>
/// Gets the client instance this object is tied to.
/// </summary>
[JsonIgnore]
internal BaseDiscordClient Discord { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="SnowflakeObject"/> class.
/// </summary>
internal SnowflakeObject() { }
}
diff --git a/DisCatSharp/Entities/Sticker/DiscordSticker.cs b/DisCatSharp/Entities/Sticker/DiscordSticker.cs
index 2c1ba0bf8..47ad401eb 100644
--- a/DisCatSharp/Entities/Sticker/DiscordSticker.cs
+++ b/DisCatSharp/Entities/Sticker/DiscordSticker.cs
@@ -1,252 +1,252 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a Discord Sticker.
/// </summary>
public class DiscordSticker : SnowflakeObject, IEquatable<DiscordSticker>
{
/// <summary>
/// Gets the Pack ID of this sticker.
/// </summary>
[JsonProperty("pack_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? PackId { get; internal set; }
/// <summary>
/// Gets the Name of the sticker.
/// </summary>
[JsonProperty("name")]
public string Name { get; internal set; }
/// <summary>
/// Gets the Description of the sticker.
/// </summary>
[JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)]
public string Description { get; internal set; }
/// <summary>
/// Gets the type of sticker.
/// </summary>
[JsonProperty("type")]
public StickerType Type { get; internal set; }
/// <summary>
/// For guild stickers, gets the user that made the sticker.
/// </summary>
[JsonProperty("user", NullValueHandling = NullValueHandling.Ignore)]
public DiscordUser User { get; internal set; }
/// <summary>
/// Gets the guild associated with this sticker, if any.
/// </summary>
public DiscordGuild Guild => (this.Discord as DiscordClient).InternalGetCachedGuild(this.GuildId);
/// <summary>
/// Gets the guild id the sticker belongs too.
/// </summary>
[JsonProperty("guild_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? GuildId { get; internal set; }
/// <summary>
/// Gets whether this sticker is available. Only applicable to guild stickers.
/// </summary>
[JsonProperty("available", NullValueHandling = NullValueHandling.Ignore)]
public bool Available { get; internal set; }
/// <summary>
/// Gets the sticker's sort order, if it's in a pack.
/// </summary>
[JsonProperty("sort_value", NullValueHandling = NullValueHandling.Ignore)]
public int? SortValue { get; internal set; }
/// <summary>
/// Gets the list of tags for the sticker.
/// </summary>
[JsonIgnore]
public IReadOnlyList<string> Tags
=> this.InternalTags != null ? this.InternalTags.Split(',') : Array.Empty<string>();
/// <summary>
/// Gets the asset hash of the sticker.
/// </summary>
[JsonProperty("asset", NullValueHandling = NullValueHandling.Ignore)]
public string Asset { get; internal set; }
/// <summary>
/// Gets the preview asset hash of the sticker.
/// </summary>
[JsonProperty("preview_asset", NullValueHandling = NullValueHandling.Ignore)]
public string PreviewAsset { get; internal set; }
/// <summary>
/// Gets the Format type of the sticker.
/// </summary>
[JsonProperty("format_type")]
public StickerFormat FormatType { get; internal set; }
/// <summary>
/// Gets the tags of the sticker.
/// </summary>
[JsonProperty("tags", NullValueHandling = NullValueHandling.Ignore)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "<Pending>")]
internal string InternalTags { get; set; }
/// <summary>
/// Gets the url of the sticker.
/// </summary>
[JsonIgnore]
public string Url => $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.STICKERS}/{this.Id}.{(this.FormatType == StickerFormat.Lottie ? "json" : "png")}";
/// <summary>
/// Initializes a new instance of the <see cref="DiscordSticker"/> class.
/// </summary>
internal DiscordSticker()
{ }
/// <summary>
/// Gets the hash code of the current sticker.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode()
=> this.PackId != null ? HashCode.Combine(this.Id.GetHashCode(), this.PackId.GetHashCode()) : HashCode.Combine(this.Id.GetHashCode(), this.GuildId.GetHashCode());
/// <summary>
/// Checks whether this <see cref="DiscordSticker"/> is equal to another object.
/// </summary>
/// <param name="obj">Object to compare to.</param>
/// <returns>Whether the object is equal to this <see cref="DiscordSticker"/>.</returns>
public override bool Equals(object obj)
=> this.Equals(obj as ForumPostTag);
/// <summary>
/// Whether to stickers are equal.
/// </summary>
/// <param name="other">DiscordSticker</param>
public bool Equals(DiscordSticker other)
=> this.Id == other.Id;
/// <summary>
/// Gets the sticker in readable format.
/// </summary>
public override string ToString() => $"Sticker {this.Id}; {this.Name}; {this.FormatType}";
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Modifies the sticker
/// </summary>
/// <param name="name">The name of the sticker</param>
/// <param name="description">The description of the sticker</param>
/// <param name="tags">The name of a unicode emoji representing the sticker's expression</param>
/// <param name="reason">Audit log reason</param>
/// <returns>A sticker object</returns>
/// <exception cref="UnauthorizedException">Thrown when the sticker could not be found.</exception>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageEmojisAndStickers"/> permission.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
/// <exception cref="ArgumentException">Sticker does not belong to a guild.</exception>
public Task<DiscordSticker> ModifyAsync(Optional<string> name, Optional<string> description, Optional<string> tags, string reason = null) =>
!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);
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
-
+
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
-/// <summary>
+ /// <summary>
/// Deletes the sticker
/// </summary>
/// <param name="reason">Audit log reason</param>
/// <exception cref="UnauthorizedException">Thrown when the sticker could not be found.</exception>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageEmojisAndStickers"/> permission.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
/// <exception cref="ArgumentException">Sticker does not belong to a guild.</exception>
public Task DeleteAsync(string reason = null)
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
=> this.GuildId.HasValue ? this.Discord.ApiClient.DeleteGuildStickerAsync(this.GuildId.Value, this.Id, reason) : throw new ArgumentException("The requested sticker is no guild sticker.");
}
/// <summary>
/// The sticker type
/// </summary>
public enum StickerType : long
{
/// <summary>
/// Standard nitro sticker
/// </summary>
Standard = 1,
/// <summary>
/// Custom guild sticker
/// </summary>
Guild = 2
}
/// <summary>
/// The sticker type
/// </summary>
public enum StickerFormat : long
{
/// <summary>
/// Sticker is a png
/// </summary>
Png = 1,
/// <summary>
/// Sticker is a animated png
/// </summary>
Apng = 2,
/// <summary>
/// Sticker is lottie
/// </summary>
Lottie = 3,
/// <summary>
/// Sticker is a gif
/// </summary>
GIF = 4
}
diff --git a/DisCatSharp/Entities/Sticker/DiscordStickerPack.cs b/DisCatSharp/Entities/Sticker/DiscordStickerPack.cs
index b816a0db6..a2458d7ce 100644
--- a/DisCatSharp/Entities/Sticker/DiscordStickerPack.cs
+++ b/DisCatSharp/Entities/Sticker/DiscordStickerPack.cs
@@ -1,86 +1,86 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a Discord sticker pack.
/// </summary>
public sealed class DiscordStickerPack : SnowflakeObject
{
/// <summary>
/// Gets the stickers contained in this pack.
/// </summary>
[JsonIgnore]
public IReadOnlyList<DiscordSticker> Stickers => this.StickersInternal;
[JsonProperty("stickers")]
internal List<DiscordSticker> StickersInternal = new();
/// <summary>
/// Gets the name of this sticker pack.
/// </summary>
[JsonProperty("name")]
public string Name { get; internal set; }
/// <summary>
/// Gets the sku id.
/// </summary>
[JsonProperty("sku_id")]
public ulong SkuId { get; internal set; }
/// <summary>
/// Gets the Id of this pack's cover sticker.
/// </summary>
[JsonProperty("cover_sticker_id")]
public ulong CoverStickerId { get; internal set; }
/// <summary>
/// Gets the pack's cover sticker.
/// </summary>
public Task<DiscordSticker> CoverSticker => this.Discord.ApiClient.GetStickerAsync(this.CoverStickerId);
/// <summary>
/// Gets the Id of this pack's banner.
/// </summary>
[JsonProperty("banner_asset_id")]
public ulong BannerAssetId { get; internal set; }
/// <summary>
/// Gets the pack's banner url.
/// </summary>
public string BannerUrl => $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.APP_ASSETS}{Endpoints.STICKER_APPLICATION}{Endpoints.STORE}/{this.BannerAssetId}.png?size=4096";
/// <summary>
/// Initializes a new instance of the <see cref="DiscordStickerPack"/> class.
/// </summary>
internal DiscordStickerPack()
{ }
}
diff --git a/DisCatSharp/Entities/User/DiscordActivity.cs b/DisCatSharp/Entities/User/DiscordActivity.cs
index c7e2d4896..93596dc14 100644
--- a/DisCatSharp/Entities/User/DiscordActivity.cs
+++ b/DisCatSharp/Entities/User/DiscordActivity.cs
@@ -1,528 +1,528 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net.Abstractions;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents user status.
/// </summary>
[JsonConverter(typeof(UserStatusConverter))]
public enum UserStatus
{
/// <summary>
/// User is offline.
/// </summary>
Offline = 0,
/// <summary>
/// User is online.
/// </summary>
Online = 1,
/// <summary>
/// User is idle.
/// </summary>
Idle = 2,
/// <summary>
/// User asked not to be disturbed.
/// </summary>
DoNotDisturb = 4,
/// <summary>
/// User is invisible. They will appear as Offline to anyone but themselves.
/// </summary>
Invisible = 5,
/// <summary>
/// User is streaming.
/// </summary>
Streaming = 6
}
/// <summary>
/// Represents a user status converter.
/// </summary>
internal sealed class UserStatusConverter : JsonConverter
{
/// <summary>
/// Writes the json.
/// </summary>
/// <param name="writer">The writer.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The serializer.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is UserStatus status)
{
switch (status) // reader.Value can be a string, DateTime or DateTimeOffset (yes, it's weird)
{
case UserStatus.Online:
writer.WriteValue("online");
return;
case UserStatus.Idle:
writer.WriteValue("idle");
return;
case UserStatus.DoNotDisturb:
writer.WriteValue("dnd");
return;
case UserStatus.Invisible:
writer.WriteValue("invisible");
return;
case UserStatus.Streaming:
writer.WriteValue("streaming");
return;
case UserStatus.Offline:
default:
writer.WriteValue("offline");
return;
}
}
}
/// <summary>
/// Reads the json.
/// </summary>
/// <param name="reader">The reader.</param>
/// <param name="objectType">The object type.</param>
/// <param name="existingValue">The existing value.</param>
/// <param name="serializer">The serializer.</param>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) =>
// Active sessions are indicated with an "online", "idle", or "dnd" string per platform. If a user is
// offline or invisible, the corresponding field is not present.
reader.Value?.ToString().ToLowerInvariant() switch // reader.Value can be a string, DateTime or DateTimeOffset (yes, it's weird)
{
"online" => UserStatus.Online,
"idle" => UserStatus.Idle,
"dnd" => UserStatus.DoNotDisturb,
"invisible" => UserStatus.Invisible,
"streaming" => UserStatus.Streaming,
_ => UserStatus.Offline,
};
/// <summary>
/// Whether this user5 status can be converted.
/// </summary>
/// <param name="objectType">The object type.</param>
/// <returns>A bool.</returns>
public override bool CanConvert(Type objectType) => objectType == typeof(UserStatus);
}
/// <summary>
/// Represents a game that a user is playing.
/// </summary>
public sealed class DiscordActivity
{
/// <summary>
/// Gets or sets the id of user's activity.
/// </summary>
public string Id { get; set; }
/// <summary>
/// Gets or sets the name of user's activity.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets the stream URL, if applicable.
/// </summary>
public string StreamUrl { get; set; }
/// <summary>
/// Gets or sets platform in this rich presence.
/// </summary>
public string Platform { get; set; }
/// <summary>
/// Gets or sets sync id in this rich presence.
/// </summary>
public string SyncId { get; set; }
/// <summary>
/// Gets or sets session_id in this rich presence.
/// </summary>
public string SessionId { get; set; }
/// <summary>
/// Gets or sets the activity type.
/// </summary>
public ActivityType ActivityType { get; set; }
/// <summary>
/// Gets the rich presence details, if present.
/// </summary>
public DiscordRichPresence RichPresence { get; internal set; }
/// <summary>
/// Gets the custom status of this activity, if present.
/// </summary>
public DiscordCustomStatus CustomStatus { get; internal set; }
/// <summary>
/// Creates a new, empty instance of a <see cref="DiscordActivity"/>.
/// </summary>
public DiscordActivity()
{
this.ActivityType = ActivityType.Playing;
}
/// <summary>
/// Creates a new instance of a <see cref="DiscordActivity"/> with specified name.
/// </summary>
/// <param name="name">Name of the activity.</param>
public DiscordActivity(string name)
{
this.Name = name;
this.ActivityType = ActivityType.Playing;
}
/// <summary>
/// Creates a new instance of a <see cref="DiscordActivity"/> with specified name.
/// </summary>
/// <param name="name">Name of the activity.</param>
/// <param name="type">Type of the activity.</param>
public DiscordActivity(string name, ActivityType type)
{
if (type == ActivityType.Custom)
throw new InvalidOperationException("Bots cannot use a custom status.");
this.Name = name;
this.ActivityType = type;
}
/// <summary>
/// Initializes a new instance of the <see cref="DiscordActivity"/> class.
/// </summary>
/// <param name="rawActivity">The raw activity.</param>
internal DiscordActivity(TransportActivity rawActivity)
{
this.UpdateWith(rawActivity);
}
/// <summary>
/// Initializes a new instance of the <see cref="DiscordActivity"/> class.
/// </summary>
/// <param name="other">The other.</param>
internal DiscordActivity(DiscordActivity other)
{
this.Name = other.Name;
this.ActivityType = other.ActivityType;
this.StreamUrl = other.StreamUrl;
this.SessionId = other.SessionId;
this.SyncId = other.SyncId;
this.Platform = other.Platform;
this.RichPresence = new DiscordRichPresence(other.RichPresence);
this.CustomStatus = new DiscordCustomStatus(other.CustomStatus);
}
/// <summary>
/// Updates a activity with an transport activity.
/// </summary>
/// <param name="rawActivity">The raw activity.</param>
internal void UpdateWith(TransportActivity rawActivity)
{
this.Name = rawActivity?.Name;
this.ActivityType = rawActivity != null ? rawActivity.ActivityType : ActivityType.Playing;
this.StreamUrl = rawActivity?.StreamUrl;
this.SessionId = rawActivity?.SessionId;
this.SyncId = rawActivity?.SyncId;
this.Platform = rawActivity?.Platform;
if (rawActivity?.IsRichPresence() == true && this.RichPresence != null)
this.RichPresence.UpdateWith(rawActivity);
else this.RichPresence = rawActivity?.IsRichPresence() == true ? new DiscordRichPresence(rawActivity) : null;
if (rawActivity?.IsCustomStatus() == true && this.CustomStatus != null)
this.CustomStatus.UpdateWith(rawActivity.State, rawActivity.Emoji);
else this.CustomStatus = rawActivity?.IsCustomStatus() == true
? new DiscordCustomStatus
{
Name = rawActivity.State,
Emoji = rawActivity.Emoji
}
: null;
}
}
/// <summary>
/// Represents details for a custom status activity, attached to a <see cref="DiscordActivity"/>.
/// </summary>
public sealed class DiscordCustomStatus
{
/// <summary>
/// Gets the name of this custom status.
/// </summary>
public string Name { get; internal set; }
/// <summary>
/// Gets the emoji of this custom status, if any.
/// </summary>
public DiscordEmoji Emoji { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordCustomStatus"/> class.
/// </summary>
internal DiscordCustomStatus()
{ }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordCustomStatus"/> class.
/// </summary>
/// <param name="other">The other.</param>
internal DiscordCustomStatus(DiscordCustomStatus other)
{
this.Name = other.Name;
this.Emoji = other.Emoji;
}
/// <summary>
/// Updates a discord status.
/// </summary>
/// <param name="state">The state.</param>
/// <param name="emoji">The emoji.</param>
internal void UpdateWith(string state, DiscordEmoji emoji)
{
this.Name = state;
this.Emoji = emoji;
}
}
/// <summary>
/// Represents details for Discord rich presence, attached to a <see cref="DiscordActivity"/>.
/// </summary>
public sealed class DiscordRichPresence
{
/// <summary>
/// Gets the details of this presence.
/// </summary>
public string Details { get; internal set; }
/// <summary>
/// Gets the game state.
/// </summary>
public string State { get; internal set; }
/// <summary>
/// Gets the application for which the rich presence is for.
/// </summary>
public DiscordApplication Application { get; internal set; }
/// <summary>
/// Gets the instance status.
/// </summary>
public bool? Instance { get; internal set; }
/// <summary>
/// Gets the large image for the rich presence.
/// </summary>
public DiscordAsset LargeImage { get; internal set; }
/// <summary>
/// Gets the hover text for large image.
/// </summary>
public string LargeImageText { get; internal set; }
/// <summary>
/// Gets the small image for the rich presence.
/// </summary>
public DiscordAsset SmallImage { get; internal set; }
/// <summary>
/// Gets the hover text for small image.
/// </summary>
public string SmallImageText { get; internal set; }
/// <summary>
/// Gets the current party size.
/// </summary>
public long? CurrentPartySize { get; internal set; }
/// <summary>
/// Gets the maximum party size.
/// </summary>
public long? MaximumPartySize { get; internal set; }
/// <summary>
/// Gets the party ID.
/// </summary>
public ulong? PartyId { get; internal set; }
/// <summary>
/// Gets the buttons.
/// </summary>
public IReadOnlyList<string> Buttons { get; internal set; }
/// <summary>
/// Gets the game start timestamp.
/// </summary>
public DateTimeOffset? StartTimestamp { get; internal set; }
/// <summary>
/// Gets the game end timestamp.
/// </summary>
public DateTimeOffset? EndTimestamp { get; internal set; }
/// <summary>
/// Gets the secret value enabling users to join your game.
/// </summary>
public string JoinSecret { get; internal set; }
/// <summary>
/// Gets the secret value enabling users to receive notifications whenever your game state changes.
/// </summary>
public string MatchSecret { get; internal set; }
/// <summary>
/// Gets the secret value enabling users to spectate your game.
/// </summary>
public string SpectateSecret { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordRichPresence"/> class.
/// </summary>
internal DiscordRichPresence() { }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordRichPresence"/> class.
/// </summary>
/// <param name="rawGame">The raw game.</param>
internal DiscordRichPresence(TransportActivity rawGame)
{
this.UpdateWith(rawGame);
}
/// <summary>
/// Initializes a new instance of the <see cref="DiscordRichPresence"/> class.
/// </summary>
/// <param name="other">The other.</param>
internal DiscordRichPresence(DiscordRichPresence other)
{
this.Details = other.Details;
this.State = other.State;
this.Application = other.Application;
this.Instance = other.Instance;
this.LargeImageText = other.LargeImageText;
this.SmallImageText = other.SmallImageText;
this.LargeImage = other.LargeImage;
this.SmallImage = other.SmallImage;
this.CurrentPartySize = other.CurrentPartySize;
this.MaximumPartySize = other.MaximumPartySize;
this.PartyId = other.PartyId;
this.Buttons = other.Buttons;
this.StartTimestamp = other.StartTimestamp;
this.EndTimestamp = other.EndTimestamp;
this.JoinSecret = other.JoinSecret;
this.MatchSecret = other.MatchSecret;
this.SpectateSecret = other.SpectateSecret;
}
/// <summary>
/// Updates a game activity with an transport activity.
/// </summary>
/// <param name="rawGame">The raw game.</param>
internal void UpdateWith(TransportActivity rawGame)
{
this.Details = rawGame?.Details;
this.State = rawGame?.State;
this.Application = rawGame?.ApplicationId != null ? new DiscordApplication { Id = rawGame.ApplicationId.Value } : null;
this.Instance = rawGame?.Instance;
this.LargeImageText = rawGame?.Assets?.LargeImageText;
this.SmallImageText = rawGame?.Assets?.SmallImageText;
this.CurrentPartySize = rawGame?.Party?.Size?.Current;
this.MaximumPartySize = rawGame?.Party?.Size?.Maximum;
if (rawGame?.Party != null && ulong.TryParse(rawGame.Party.Id, NumberStyles.Number, CultureInfo.InvariantCulture, out var partyId))
this.PartyId = partyId;
this.Buttons = rawGame?.Buttons;
this.StartTimestamp = rawGame?.Timestamps?.Start;
this.EndTimestamp = rawGame?.Timestamps?.End;
this.JoinSecret = rawGame?.Secrets?.Join;
this.MatchSecret = rawGame?.Secrets?.Match;
this.SpectateSecret = rawGame?.Secrets?.Spectate;
var lid = rawGame?.Assets?.LargeImage;
if (lid != null)
{
if (lid.StartsWith("spotify:"))
this.LargeImage = new DiscordSpotifyAsset { Id = lid };
else if (ulong.TryParse(lid, NumberStyles.Number, CultureInfo.InvariantCulture, out var ulid))
this.LargeImage = new DiscordApplicationAsset { Id = lid, Application = this.Application, Type = ApplicationAssetType.LargeImage };
}
var sid = rawGame?.Assets?.SmallImage;
if (sid != null)
{
if (sid.StartsWith("spotify:"))
this.SmallImage = new DiscordSpotifyAsset { Id = sid };
else if (ulong.TryParse(sid, NumberStyles.Number, CultureInfo.InvariantCulture, out var usid))
this.SmallImage = new DiscordApplicationAsset { Id = sid, Application = this.Application, Type = ApplicationAssetType.LargeImage };
}
}
}
/// <summary>
/// Determines the type of a user activity.
/// </summary>
public enum ActivityType
{
/// <summary>
/// Indicates the user is playing a game.
/// </summary>
Playing = 0,
/// <summary>
/// Indicates the user is streaming a game.
/// </summary>
Streaming = 1,
/// <summary>
/// Indicates the user is listening to something.
/// </summary>
ListeningTo = 2,
/// <summary>
/// Indicates the user is watching something.
/// </summary>
Watching = 3,
/// <summary>
/// Indicates the current activity is a custom status.
/// </summary>
Custom = 4,
/// <summary>
/// Indicates the user is competing in something.
/// </summary>
Competing = 5
}
diff --git a/DisCatSharp/Entities/User/DiscordConnection.cs b/DisCatSharp/Entities/User/DiscordConnection.cs
index 246eccd03..d06c37980 100644
--- a/DisCatSharp/Entities/User/DiscordConnection.cs
+++ b/DisCatSharp/Entities/User/DiscordConnection.cs
@@ -1,208 +1,208 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Gets a Discord connection to a 3rd party service.
/// </summary>
public class DiscordConnection
{
/// <summary>
/// Gets the id of the connection account
/// </summary>
[JsonProperty("id")]
public string Id { get; internal set; }
/// <summary>
/// Gets the username of the connection account.
/// </summary>
[JsonProperty("name")]
public string Name { get; internal set; }
/// <summary>
/// <para>Gets the service of the connection.</para>
/// <list type="table">
/// <listheader>
/// <term>Type</term>
/// <description>Obsolete (Non-assignable)</description>
/// </listheader>
/// <item>
/// <term>contacts</term>
/// <description>false</description>
/// </item>
/// <item>
/// <term>instagram</term>
/// <description>false</description>
/// </item>
/// <item>
/// <term>crunchyroll</term>
/// <description>false</description>
/// </item>
/// <item>
/// <term>tiktok</term>
/// <description>false</description>
/// </item>
/// <item>
/// <term>paypal</term>
/// <description>false</description>
/// </item>
/// <item>
/// <term>epicgames</term>
/// <description>false</description>
/// </item>
/// <item>
/// <term>ebay</term>
/// <description>false</description>
/// </item>
/// <item>
/// <term>twitch</term>
/// <description>false</description>
/// </item>
/// <item>
/// <term>steam</term>
/// <description>false</description>
/// </item>
/// <item>
/// <term>youtube</term>
/// <description>false</description>
/// </item>
/// <item>
/// <term>twitter</term>
/// <description>false</description>
/// </item>
/// <item>
/// <term>facebook</term>
/// <description>false</description>
/// </item>
/// <item>
/// <term>spotify</term>
/// <description>false</description>
/// </item>
/// <item>
/// <term>xbox</term>
/// <description>false</description>
/// </item>
/// <item>
/// <term>playstation</term>
/// <description>false</description>
/// </item>
/// <item>
/// <term>epicgames</term>
/// <description>false</description>
/// </item>
/// <item>
/// <term>reddit</term>
/// <description>false</description>
/// </item>
/// <item>
/// <term>battlenet</term>
/// <description>false</description>
/// </item>
/// <item>
/// <term>github</term>
/// <description>false</description>
/// </item>
/// <item>
/// <term>leagueoflegends</term>
/// <description>false</description>
/// </item>
/// <item>
/// <term>skype</term>
/// <description>true</description>
/// </item>
/// <item>
/// <term>samsunggalaxy</term>
/// <description>true</description>
/// </item>
/// </list>
/// </summary>
[JsonProperty("type")]
public string Type { get; internal set; }
/// <summary>
/// Gets whether the connection is revoked.
/// </summary>
[JsonProperty("revoked", NullValueHandling = NullValueHandling.Include)]
public bool? IsRevoked { get; internal set; }
/// <summary>
/// Gets a collection of partial server integrations.
/// </summary>
[JsonProperty("integrations", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<DiscordIntegration> Integrations { get; internal set; }
/// <summary>
/// Gets whether the connection is verified.
/// </summary>
[JsonProperty("verified", NullValueHandling = NullValueHandling.Ignore)]
public bool Verified { get; internal set; }
/// <summary>
/// Gets whether the connection will show a activity.
/// </summary>
[JsonProperty("show_activity", NullValueHandling = NullValueHandling.Ignore)]
public bool ShowActivity { get; internal set; }
/// <summary>
/// Whether the connection will sync friends.
/// </summary>
[JsonProperty("friend_sync", NullValueHandling = NullValueHandling.Ignore)]
public bool? FriendSync { get; internal set; }
/// <summary>
/// Whether this connection supports console voice transfer.
/// Currently in beta rollout for XBox. Playstation soon.
/// </summary>
[JsonProperty("two_way_link", NullValueHandling = NullValueHandling.Ignore)]
public bool TwoWayLink { get; internal set; }
/// <summary>
/// Gets the visibility of the connection.
/// </summary>
[JsonProperty("visibility", NullValueHandling = NullValueHandling.Ignore)]
public ConnectionVisibilityType Visibility { get; internal set; }
/// <summary>
/// Gets the metadata visibility of the connection.
/// </summary>
[JsonProperty("metadata_visibility", NullValueHandling = NullValueHandling.Ignore)]
public ConnectionMetadataVisibilityType MetadataVisibility { get; internal set; }
/// <summary>
/// Gets the client instance this object is tied to.
/// </summary>
[JsonIgnore]
internal BaseDiscordClient Discord { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordConnection"/> class.
/// </summary>
internal DiscordConnection()
{ }
}
diff --git a/DisCatSharp/Entities/User/DiscordPresence.cs b/DisCatSharp/Entities/User/DiscordPresence.cs
index 8f04b82d5..e1f9c5da6 100644
--- a/DisCatSharp/Entities/User/DiscordPresence.cs
+++ b/DisCatSharp/Entities/User/DiscordPresence.cs
@@ -1,160 +1,160 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a user presence.
/// </summary>
public sealed class DiscordPresence
{
/// <summary>
/// Gets the discord client.
/// </summary>
[JsonIgnore]
internal DiscordClient Discord { get; set; }
/// <summary>
/// Gets the internal user.
/// </summary>
[JsonProperty("user")]
internal UserWithIdOnly InternalUser { get; set; }
/// <summary>
/// Gets the user that owns this presence.
/// </summary>
[JsonIgnore]
public DiscordUser User
=> this.Discord.GetCachedOrEmptyUserInternal(this.InternalUser.Id);
/// <summary>
/// Gets the user's current activity.
/// </summary>
[JsonIgnore]
public DiscordActivity Activity { get; internal set; }
/// <summary>
/// Gets the raw activity.
/// </summary>
internal TransportActivity RawActivity { get; set; }
/// <summary>
/// Gets the user's current activities.
/// </summary>
[JsonIgnore]
public IReadOnlyList<DiscordActivity> Activities
=> this.InternalActivities;
[JsonIgnore]
internal DiscordActivity[] InternalActivities;
/// <summary>
/// Gets the raw activities.
/// </summary>
[JsonProperty("activities", NullValueHandling = NullValueHandling.Ignore)]
internal TransportActivity[] RawActivities { get; set; }
/// <summary>
/// Gets this user's status.
/// </summary>
[JsonProperty("status", NullValueHandling = NullValueHandling.Ignore)]
public UserStatus Status { get; internal set; }
/// <summary>
/// Gets the guild id for which this presence was set.
/// </summary>
[JsonProperty("guild_id", NullValueHandling = NullValueHandling.Ignore)]
internal ulong GuildId { get; set; }
/// <summary>
/// Gets the guild for which this presence was set.
/// </summary>
[JsonIgnore]
public DiscordGuild Guild
=> this.GuildId != 0 ? this.Discord.GuildsInternal[this.GuildId] : null;
/// <summary>
/// Gets this user's platform-dependent status.
/// </summary>
[JsonProperty("client_status", NullValueHandling = NullValueHandling.Ignore)]
public DiscordClientStatus ClientStatus { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordPresence"/> class.
/// </summary>
internal DiscordPresence()
{ }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordPresence"/> class.
/// </summary>
/// <param name="other">The other.</param>
internal DiscordPresence(DiscordPresence other)
{
this.Discord = other.Discord;
this.Activity = other.Activity;
this.RawActivity = other.RawActivity;
this.InternalActivities = (DiscordActivity[])other.InternalActivities?.Clone();
this.RawActivities = (TransportActivity[])other.RawActivities?.Clone();
this.Status = other.Status;
this.InternalUser = other.InternalUser;
}
}
/// <summary>
/// Represents a user with only its id.
/// </summary>
public sealed class UserWithIdOnly
{
[JsonProperty("id")]
public ulong Id { get; internal set; }
}
/// <summary>
/// Represents a client status.
/// </summary>
public sealed class DiscordClientStatus
{
/// <summary>
/// Gets the user's status set for an active desktop (Windows, Linux, Mac) application session.
/// </summary>
[JsonProperty("desktop", NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Populate)]
public Optional<UserStatus> Desktop { get; internal set; } = UserStatus.Offline;
/// <summary>
/// Gets the user's status set for an active mobile (iOS, Android) application session.
/// </summary>
[JsonProperty("mobile", NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Populate)]
public Optional<UserStatus> Mobile { get; internal set; } = UserStatus.Offline;
/// <summary>
/// Gets the user's status set for an active web (browser, bot account) application session.
/// </summary>
[JsonProperty("web", NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Populate)]
public Optional<UserStatus> Web { get; internal set; } = UserStatus.Offline;
}
diff --git a/DisCatSharp/Entities/User/DiscordTeam.cs b/DisCatSharp/Entities/User/DiscordTeam.cs
index 86f294b94..6b68f1ed4 100644
--- a/DisCatSharp/Entities/User/DiscordTeam.cs
+++ b/DisCatSharp/Entities/User/DiscordTeam.cs
@@ -1,202 +1,202 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using DisCatSharp.Net;
using DisCatSharp.Net.Abstractions;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a team consisting of users. A team can own an application.
/// </summary>
public sealed class DiscordTeam : SnowflakeObject, IEquatable<DiscordTeam>
{
/// <summary>
/// Gets the team's name.
/// </summary>
public string Name { get; internal set; }
/// <summary>
/// Gets the team's icon hash.
/// </summary>
public string IconHash { get; internal set; }
/// <summary>
/// Gets the team's icon.
/// </summary>
public string Icon
=> !string.IsNullOrWhiteSpace(this.IconHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.TEAM_ICONS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.IconHash}.png?size=1024" : null;
/// <summary>
/// Gets the owner of the team.
/// </summary>
public DiscordUser Owner { get; internal set; }
/// <summary>
/// Gets the members of this team.
/// </summary>
public IReadOnlyList<DiscordTeamMember> Members { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordTeam"/> class.
/// </summary>
/// <param name="tt">The tt.</param>
internal DiscordTeam(TransportTeam tt)
{
this.Id = tt.Id;
this.Name = tt.Name;
this.IconHash = tt.IconHash;
}
/// <summary>
/// Compares this team to another object and returns whether they are equal.
/// </summary>
/// <param name="obj">Object to compare this team to.</param>
/// <returns>Whether this team is equal to the given object.</returns>
public override bool Equals(object obj)
=> obj is DiscordTeam other && this == other;
/// <summary>
/// Compares this team to another team and returns whether they are equal.
/// </summary>
/// <param name="other">Team to compare to.</param>
/// <returns>Whether the teams are equal.</returns>
public bool Equals(DiscordTeam other)
=> this == other;
/// <summary>
/// Gets the hash code of this team.
/// </summary>
/// <returns>Hash code of this team.</returns>
public override int GetHashCode()
=> this.Id.GetHashCode();
/// <summary>
/// Converts this team to its string representation.
/// </summary>
/// <returns>The string representation of this team.</returns>
public override string ToString()
=> $"Team: {this.Name} ({this.Id})";
public static bool operator ==(DiscordTeam left, DiscordTeam right)
=> left?.Id == right?.Id;
public static bool operator !=(DiscordTeam left, DiscordTeam right)
=> left?.Id != right?.Id;
}
/// <summary>
/// Represents a member of <see cref="DiscordTeam"/>.
/// </summary>
public sealed class DiscordTeamMember : IEquatable<DiscordTeamMember>
{
/// <summary>
/// Gets the member's membership status.
/// </summary>
public DiscordTeamMembershipStatus MembershipStatus { get; internal set; }
/// <summary>
/// Gets the member's permissions within the team.
/// </summary>
public IReadOnlyCollection<string> Permissions { get; internal set; }
/// <summary>
/// Gets the id of the team this member belongs to.
/// </summary>
public ulong? TeamId { get; internal set; }
/// <summary>
/// Gets the name of the team this member belongs to.
/// </summary>
public string TeamName { get; internal set; }
/// <summary>
/// Gets the user who is the team member.
/// </summary>
public DiscordUser User { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordTeamMember"/> class.
/// </summary>
/// <param name="ttm">The ttm.</param>
internal DiscordTeamMember(TransportTeamMember ttm)
{
this.MembershipStatus = (DiscordTeamMembershipStatus)ttm.MembershipState;
this.Permissions = new ReadOnlySet<string>(new HashSet<string>(ttm.Permissions));
}
/// <summary>
/// Compares this team member to another object and returns whether they are equal.
/// </summary>
/// <param name="obj">Object to compare to.</param>
/// <returns>Whether this team is equal to given object.</returns>
public override bool Equals(object obj)
=> obj is DiscordTeamMember other && this == other;
/// <summary>
/// Compares this team member to another team member and returns whether they are equal.
/// </summary>
/// <param name="other">Team member to compare to.</param>
/// <returns>Whether this team member is equal to the given one.</returns>
public bool Equals(DiscordTeamMember other)
=> this == other;
/// <summary>
/// Gets a hash code of this team member.
/// </summary>
/// <returns>Hash code of this team member.</returns>
public override int GetHashCode() => HashCode.Combine(this.User, this.TeamId);
/// <summary>
/// Converts this team member to their string representation.
/// </summary>
/// <returns>String representation of this team member.</returns>
public override string ToString()
=> $"Team member: {this.User.Username}#{this.User.Discriminator} ({this.User.Id}), part of team {this.TeamName} ({this.TeamId})";
public static bool operator ==(DiscordTeamMember left, DiscordTeamMember right)
=> left?.TeamId == right?.TeamId && left?.User?.Id == right?.User?.Id;
public static bool operator !=(DiscordTeamMember left, DiscordTeamMember right)
=> left?.TeamId != right?.TeamId || left?.User?.Id != right?.User?.Id;
}
/// <summary>
/// Signifies the status of user's team membership.
/// </summary>
public enum DiscordTeamMembershipStatus : int
{
/// <summary>
/// Indicates that this user is invited to the team, and is pending membership.
/// </summary>
Invited = 1,
/// <summary>
/// Indicates that this user is a member of the team.
/// </summary>
Accepted = 2
}
diff --git a/DisCatSharp/Entities/User/DiscordUser.cs b/DisCatSharp/Entities/User/DiscordUser.cs
index 3510dbacf..0ec7fa50b 100644
--- a/DisCatSharp/Entities/User/DiscordUser.cs
+++ b/DisCatSharp/Entities/User/DiscordUser.cs
@@ -1,607 +1,607 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Threading.Tasks;
using DisCatSharp.Enums;
using DisCatSharp.Exceptions;
using DisCatSharp.Net;
using DisCatSharp.Net.Abstractions;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents a Discord user.
/// </summary>
public class DiscordUser : SnowflakeObject, IEquatable<DiscordUser>
{
/// <summary>
/// Initializes a new instance of the <see cref="DiscordUser"/> class.
/// </summary>
internal DiscordUser()
{ }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordUser"/> class.
/// </summary>
/// <param name="transport">The transport user.</param>
internal DiscordUser(TransportUser transport)
{
this.Id = transport.Id;
this.Username = transport.Username;
this.Discriminator = transport.Discriminator;
this.AvatarHash = transport.AvatarHash;
this.AvatarDecorationHash = transport.AvatarDecorationHash;
this.BannerHash = transport.BannerHash;
this.BannerColorInternal = transport.BannerColor;
this.ThemeColorsInternal = (transport.ThemeColors ?? Array.Empty<int>()).ToList();
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;
this.Pronouns = transport.Pronouns;
}
/// <summary>
/// Gets this user's username.
/// </summary>
[JsonProperty("username", NullValueHandling = NullValueHandling.Ignore)]
public virtual string Username { get; internal set; }
/// <summary>
/// Gets this user's username with the discriminator.
/// Example: Discord#0000
/// </summary>
[JsonIgnore]
public virtual string UsernameWithDiscriminator
=> $"{this.Username}#{this.Discriminator}";
/// <summary>
/// Gets the user's 4-digit discriminator.
/// </summary>
[JsonProperty("discriminator", NullValueHandling = NullValueHandling.Ignore)]
public virtual string Discriminator { get; internal set; }
/// <summary>
/// Gets the discriminator integer.
/// </summary>
[JsonIgnore]
internal int DiscriminatorInt
=> int.Parse(this.Discriminator, NumberStyles.Integer, CultureInfo.InvariantCulture);
/// <summary>
/// Gets the user's banner color, if set. Mutually exclusive with <see cref="BannerHash"/>.
/// </summary>
[JsonIgnore]
public virtual DiscordColor? BannerColor
=> !this.BannerColorInternal.HasValue ? null : new DiscordColor(this.BannerColorInternal.Value);
/// <summary>
/// Gets the user's theme colors, if set.
/// </summary>
[JsonIgnore]
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public virtual IReadOnlyList<DiscordColor>? ThemeColors
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
=> !(this.ThemeColorsInternal is not null && this.ThemeColorsInternal.Count != 0) ? null : this.ThemeColorsInternal.Select(x => new DiscordColor(x)).ToList();
/// <summary>
/// Gets the user's banner color integer.
/// </summary>
[JsonProperty("accent_color")]
internal int? BannerColorInternal;
/// <summary>
/// Gets the user's theme color integers.
/// </summary>
[JsonProperty("theme_colors", NullValueHandling = NullValueHandling.Ignore)]
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
internal List<int>? ThemeColorsInternal;
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
/// <summary>
/// Gets the user's banner url
/// </summary>
[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";
/// <summary>
/// Gets the user's profile banner hash. Mutually exclusive with <see cref="BannerColor"/>.
/// </summary>
[JsonProperty("banner", NullValueHandling = NullValueHandling.Ignore)]
public virtual string BannerHash { get; internal set; }
/// <summary>
/// Gets the users bio.
/// This is not available to bots tho.
/// </summary>
[JsonProperty("bio", NullValueHandling = NullValueHandling.Ignore)]
public virtual string Bio { get; internal set; }
/// <summary>
/// Gets the user's avatar hash.
/// </summary>
[JsonProperty("avatar", NullValueHandling = NullValueHandling.Ignore)]
public virtual string AvatarHash { get; internal set; }
/// <summary>
/// Gets the user's avatar decoration hash.
/// </summary>
[JsonProperty("avatar_decoration", NullValueHandling = NullValueHandling.Ignore)]
public virtual string AvatarDecorationHash { get; internal set; }
/// <summary>
/// Returns a uri to this users profile.
/// </summary>
[JsonIgnore]
public Uri ProfileUri => new($"{DiscordDomain.GetDomain(CoreDomain.Discord).Url}{Endpoints.USERS}/{this.Id}");
/// <summary>
/// Returns a string representing the direct URL to this users profile.
/// </summary>
/// <returns>The URL of this users profile.</returns>
[JsonIgnore]
public string ProfileUrl => this.ProfileUri.AbsoluteUri;
/// <summary>
/// Gets the user's avatar url.
/// </summary>
[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";
/// <summary>
/// Gets the user's avatar decoration url.
/// </summary>
[JsonIgnore]
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public string? AvatarDecorationUrl
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
=> string.IsNullOrWhiteSpace(this.AvatarDecorationHash) ? null : $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.AVATARS_DECORATIONS}/{this.Id.ToString(CultureInfo.InvariantCulture)}/{this.AvatarDecorationHash}.{(this.AvatarDecorationHash.StartsWith("a_") ? "gif" : "png")}?size=1024";
/// <summary>
/// Gets the URL of default avatar for this user.
/// </summary>
[JsonIgnore]
public string DefaultAvatarUrl
=> $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.EMBED}{Endpoints.AVATARS}/{(this.DiscriminatorInt % 5).ToString(CultureInfo.InvariantCulture)}.png?size=1024";
/// <summary>
/// Gets whether the user is a bot.
/// </summary>
[JsonProperty("bot", NullValueHandling = NullValueHandling.Ignore)]
public virtual bool IsBot { get; internal set; }
/// <summary>
/// Gets whether the user has multi-factor authentication enabled.
/// </summary>
[JsonProperty("mfa_enabled", NullValueHandling = NullValueHandling.Ignore)]
public virtual bool? MfaEnabled { get; internal set; }
/// <summary>
/// Gets whether the user is an official Discord system user.
/// </summary>
[JsonProperty("system", NullValueHandling = NullValueHandling.Ignore)]
public bool? IsSystem { get; internal set; }
/// <summary>
/// Gets whether the user is verified.
/// <para>This is only present in OAuth.</para>
/// </summary>
[JsonProperty("verified", NullValueHandling = NullValueHandling.Ignore)]
public virtual bool? Verified { get; internal set; }
/// <summary>
/// Gets the user's email address.
/// <para>This is only present in OAuth.</para>
/// </summary>
[JsonProperty("email", NullValueHandling = NullValueHandling.Ignore)]
public virtual string Email { get; internal set; }
/// <summary>
/// Gets the user's premium type.
/// </summary>
[JsonProperty("premium_type", NullValueHandling = NullValueHandling.Ignore)]
public virtual PremiumType? PremiumType { get; internal set; }
/// <summary>
/// Gets the user's chosen language
/// </summary>
[JsonProperty("locale", NullValueHandling = NullValueHandling.Ignore)]
public virtual string Locale { get; internal set; }
/// <summary>
/// Gets the user's flags for OAuth.
/// </summary>
[JsonProperty("flags", NullValueHandling = NullValueHandling.Ignore)]
public virtual UserFlags? OAuthFlags { get; internal set; }
/// <summary>
/// Gets the user's flags.
/// </summary>
[JsonProperty("public_flags", NullValueHandling = NullValueHandling.Ignore)]
public virtual UserFlags? Flags { get; internal set; }
/// <summary>
/// Gets the user's pronouns.
/// </summary>
[JsonProperty("pronouns", NullValueHandling = NullValueHandling.Ignore)]
public virtual string Pronouns { get; internal set; }
/// <summary>
/// Gets the user's mention string.
/// </summary>
[JsonIgnore]
public string Mention
=> Formatter.Mention(this, this is DiscordMember);
/// <summary>
/// Gets whether this user is the Client which created this object.
/// </summary>
[JsonIgnore]
public bool IsCurrent
=> this.Id == this.Discord.CurrentUser.Id;
#region Extension of DiscordUser
/// <summary>
/// Whether this member is a <see cref="UserFlags.CertifiedModerator"/>
/// </summary>
/// <returns><see cref="bool"/></returns>
[JsonIgnore]
public bool IsMod
=> this.Flags.HasValue && this.Flags.Value.HasFlag(UserFlags.CertifiedModerator);
/// <summary>
/// Whether this member is a <see cref="UserFlags.Partner"/>
/// </summary>
/// <returns><see cref="bool"/></returns>
[JsonIgnore]
public bool IsPartner
=> this.Flags.HasValue && this.Flags.Value.HasFlag(UserFlags.Partner);
/// <summary>
/// Whether this member is a <see cref="UserFlags.VerifiedBot"/>
/// </summary>
/// <returns><see cref="bool"/></returns>
[JsonIgnore]
public bool IsVerifiedBot
=> this.Flags.HasValue && this.Flags.Value.HasFlag(UserFlags.VerifiedBot);
/// <summary>
/// Whether this member is a <see cref="UserFlags.VerifiedDeveloper"/>
/// </summary>
/// <returns><see cref="bool"/></returns>
[JsonIgnore]
public bool IsBotDev
=> this.Flags.HasValue && this.Flags.Value.HasFlag(UserFlags.VerifiedDeveloper);
/// <summary>
/// Whether this member is a <see cref="UserFlags.ActiveDeveloper"/>
/// </summary>
/// <returns><see cref="bool"/></returns>
[JsonIgnore]
public bool IsActiveDeveloper
=> this.Flags.HasValue && this.Flags.Value.HasFlag(UserFlags.ActiveDeveloper);
/// <summary>
/// Whether this member is a <see cref="UserFlags.Staff"/>
/// </summary>
/// <returns><see cref="bool"/></returns>
[JsonIgnore]
public bool IsStaff
=> this.Flags.HasValue && this.Flags.Value.HasFlag(UserFlags.Staff);
#endregion
/// <summary>
/// Fetches the user from the API.
/// </summary>
/// <returns>The user with fresh data from the API.</returns>
public async Task<DiscordUser> GetFromApiAsync()
=> await this.Discord.ApiClient.GetUserAsync(this.Id);
/// <summary>
/// Gets additional information about an application if the user is an bot.
/// </summary>
/// <returns>The rpc info or <see langword="null"/></returns>
/// <exception cref="NotFoundException">Thrown when the application does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public async Task<DiscordRpcApplication?> GetRpcInfoAsync()
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
=> this.IsBot ? await this.Discord.ApiClient.GetApplicationInfoAsync(this.Id) : await Task.FromResult<DiscordRpcApplication?>(null);
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
/// <summary>
/// Whether this user is in a <see cref="DiscordGuild"/>
/// </summary>
/// <example>
/// <code>
/// 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}");
/// </code>
/// results to <c>J_M_Lutra is a member of Project Nyaw~</c>.
/// </example>
/// <param name="guild"><see cref="DiscordGuild"/></param>
/// <returns><see cref="bool"/></returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "<Pending>")]
public async Task<bool> IsInGuild(DiscordGuild guild)
{
try
{
var member = await guild.GetMemberAsync(this.Id);
return member is not null;
}
catch (NotFoundException)
{
return false;
}
}
/// <summary>
/// Whether this user is not in a <see cref="DiscordGuild"/>
/// </summary>
/// <param name="guild"><see cref="DiscordGuild"/></param>
/// <returns><see cref="bool"/></returns>
public async Task<bool> IsNotInGuild(DiscordGuild guild)
=> !await this.IsInGuild(guild);
/// <summary>
/// Returns the DiscordMember in the specified <see cref="DiscordGuild"/>
/// </summary>
/// <param name="guild">The <see cref="DiscordGuild"/> to get this user on.</param>
/// <returns>The <see cref="DiscordMember"/>.</returns>
/// <exception cref="NotFoundException">Thrown when the user is not part of the guild.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordMember> ConvertToMember(DiscordGuild guild)
=> await guild.GetMemberAsync(this.Id);
/// <summary>
/// Unbans this user from a guild.
/// </summary>
/// <param name="guild">Guild to unban this user from.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.BanMembers"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the user does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task UnbanAsync(DiscordGuild guild, string reason = null)
=> guild.UnbanMemberAsync(this, reason);
/// <summary>
/// Gets this user's presence.
/// </summary>
[JsonIgnore]
public DiscordPresence Presence
=> this.Discord is DiscordClient dc && dc.Presences.TryGetValue(this.Id, out var presence) ? presence : null;
/// <summary>
/// Gets the user's avatar URL, in requested format and size.
/// </summary>
/// <param name="fmt">Format of the avatar to get.</param>
/// <param name="size">Maximum size of the avatar. Must be a power of two, minimum 16, maximum 2048.</param>
/// <returns>URL of the user's avatar.</returns>
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}";
}
}
/// <summary>
/// Creates a direct message channel to this user.
/// </summary>
/// <returns>Direct message channel to this user.</returns>
/// <exception cref="UnauthorizedException">Thrown when the user has the bot blocked, the member shares no guild with the bot, or if the member has Allow DM from server members off.</exception>
/// <exception cref="NotFoundException">Thrown when the user does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordDmChannel> CreateDmChannelAsync()
=> this.Discord.ApiClient.CreateDmAsync(this.Id);
/// <summary>
/// Sends a direct message to this user. Creates a direct message channel if one does not exist already.
/// </summary>
/// <param name="content">Content of the message to send.</param>
/// <returns>The sent message.</returns>
/// <exception cref="UnauthorizedException">Thrown when the user has the bot blocked, the member shares no guild with the bot, or if the member has Allow DM from server members off.</exception>
/// <exception cref="NotFoundException">Thrown when the user does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordMessage> 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);
}
/// <summary>
/// Sends a direct message to this user. Creates a direct message channel if one does not exist already.
/// </summary>
/// <param name="embed">Embed to attach to the message.</param>
/// <returns>The sent message.</returns>
/// <exception cref="UnauthorizedException">Thrown when the user has the bot blocked, the member shares no guild with the bot, or if the member has Allow DM from server members off.</exception>
/// <exception cref="NotFoundException">Thrown when the user does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordMessage> 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);
}
/// <summary>
/// Sends a direct message to this user. Creates a direct message channel if one does not exist already.
/// </summary>
/// <param name="content">Content of the message to send.</param>
/// <param name="embed">Embed to attach to the message.</param>
/// <returns>The sent message.</returns>
/// <exception cref="UnauthorizedException">Thrown when the user has the bot blocked, the member shares no guild with the bot, or if the member has Allow DM from server members off.</exception>
/// <exception cref="NotFoundException">Thrown when the user does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordMessage> 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);
}
/// <summary>
/// Sends a direct message to this user. Creates a direct message channel if one does not exist already.
/// </summary>
/// <param name="message">Builder to with the message.</param>
/// <returns>The sent message.</returns>
/// <exception cref="UnauthorizedException">Thrown when the user has the bot blocked, the member shares no guild with the bot, or if the member has Allow DM from server members off.</exception>
/// <exception cref="NotFoundException">Thrown when the user does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordMessage> 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);
}
/// <summary>
/// Returns a string representation of this user.
/// </summary>
/// <returns>String representation of this user.</returns>
public override string ToString() => $"User {this.Id}; {this.Username}#{this.Discriminator}";
/// <summary>
/// Checks whether this <see cref="DiscordUser"/> is equal to another object.
/// </summary>
/// <param name="obj">Object to compare to.</param>
/// <returns>Whether the object is equal to this <see cref="DiscordUser"/>.</returns>
public override bool Equals(object obj) => this.Equals(obj as DiscordUser);
/// <summary>
/// Checks whether this <see cref="DiscordUser"/> is equal to another <see cref="DiscordUser"/>.
/// </summary>
/// <param name="e"><see cref="DiscordUser"/> to compare to.</param>
/// <returns>Whether the <see cref="DiscordUser"/> is equal to this <see cref="DiscordUser"/>.</returns>
public bool Equals(DiscordUser e) => e is not null && (ReferenceEquals(this, e) || this.Id == e.Id);
/// <summary>
/// Gets the hash code for this <see cref="DiscordUser"/>.
/// </summary>
/// <returns>The hash code for this <see cref="DiscordUser"/>.</returns>
public override int GetHashCode() => this.Id.GetHashCode();
/// <summary>
/// Gets whether the two <see cref="DiscordUser"/> objects are equal.
/// </summary>
/// <param name="e1">First user to compare.</param>
/// <param name="e2">Second user to compare.</param>
/// <returns>Whether the two users are equal.</returns>
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);
}
/// <summary>
/// Gets whether the two <see cref="DiscordUser"/> objects are not equal.
/// </summary>
/// <param name="e1">First user to compare.</param>
/// <param name="e2">Second user to compare.</param>
/// <returns>Whether the two users are not equal.</returns>
public static bool operator !=(DiscordUser e1, DiscordUser e2)
=> !(e1 == e2);
}
/// <summary>
/// Represents a user comparer.
/// </summary>
internal class DiscordUserComparer : IEqualityComparer<DiscordUser>
{
/// <summary>
/// Whether the users are equal.
/// </summary>
/// <param name="x">The first user</param>
/// <param name="y">The second user.</param>
public bool Equals(DiscordUser x, DiscordUser y) => x.Equals(y);
/// <summary>
/// Gets the hash code.
/// </summary>
/// <param name="obj">The user.</param>
public int GetHashCode(DiscordUser obj) => obj.Id.GetHashCode();
}
diff --git a/DisCatSharp/Entities/Voice/DiscordVoiceRegion.cs b/DisCatSharp/Entities/Voice/DiscordVoiceRegion.cs
index 0b7531de7..377504f62 100644
--- a/DisCatSharp/Entities/Voice/DiscordVoiceRegion.cs
+++ b/DisCatSharp/Entities/Voice/DiscordVoiceRegion.cs
@@ -1,120 +1,120 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents information about a Discord voice server region.
/// </summary>
public class DiscordVoiceRegion
{
/// <summary>
/// Gets the unique ID for the region.
/// </summary>
[JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)]
public string Id { get; internal set; }
/// <summary>
/// Gets the name of the region.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; internal set; }
/// <summary>
/// Gets an example server hostname for this region.
/// </summary>
[JsonProperty("sample_hostname", NullValueHandling = NullValueHandling.Ignore)]
public string SampleHostname { get; internal set; }
/// <summary>
/// Gets an example server port for this region.
/// </summary>
[JsonProperty("sample_port", NullValueHandling = NullValueHandling.Ignore)]
public int SamplePort { get; internal set; }
/// <summary>
/// Gets whether this region is the most optimal for the current user.
/// </summary>
[JsonProperty("optimal", NullValueHandling = NullValueHandling.Ignore)]
public bool IsOptimal { get; internal set; }
/// <summary>
/// Gets whether this voice region is deprecated.
/// </summary>
[JsonProperty("deprecated", NullValueHandling = NullValueHandling.Ignore)]
public bool IsDeprecated { get; internal set; }
/// <summary>
/// Gets whether this is a custom voice region.
/// </summary>
[JsonProperty("custom", NullValueHandling = NullValueHandling.Ignore)]
public bool IsCustom { get; internal set; }
/// <summary>
/// Gets whether two <see cref="DiscordVoiceRegion"/>s are equal.
/// </summary>
/// <param name="region">The region to compare with.</param>
public bool Equals(DiscordVoiceRegion region)
=> this == region;
/// <summary>
/// Whether two regions are equal.
/// </summary>
/// <param name="obj">A voice region.</param>
public override bool Equals(object obj) => this.Equals(obj as DiscordVoiceRegion);
/// <summary>
/// Gets the hash code.
/// </summary>
public override int GetHashCode() => this.Id.GetHashCode();
/// <summary>
/// Gets whether the two <see cref="DiscordVoiceRegion"/> objects are equal.
/// </summary>
/// <param name="left">First voice region to compare.</param>
/// <param name="right">Second voice region to compare.</param>
/// <returns>Whether the two voice regions are equal.</returns>
public static bool operator ==(DiscordVoiceRegion left, DiscordVoiceRegion right)
{
var o1 = left as object;
var o2 = right as object;
return (o1 != null || o2 == null) && (o1 == null || o2 != null) && ((o1 == null && o2 == null) || left.Id == right.Id);
}
/// <summary>
/// Gets whether the two <see cref="DiscordVoiceRegion"/> objects are not equal.
/// </summary>
/// <param name="left">First voice region to compare.</param>
/// <param name="right">Second voice region to compare.</param>
/// <returns>Whether the two voice regions are not equal.</returns>
public static bool operator !=(DiscordVoiceRegion left, DiscordVoiceRegion right)
=> !(left == right);
/// <summary>
/// Initializes a new instance of the <see cref="DiscordVoiceRegion"/> class.
/// </summary>
internal DiscordVoiceRegion()
{ }
}
diff --git a/DisCatSharp/Entities/Voice/DiscordVoiceState.cs b/DisCatSharp/Entities/Voice/DiscordVoiceState.cs
index 68d56f6de..c4a2b3300 100644
--- a/DisCatSharp/Entities/Voice/DiscordVoiceState.cs
+++ b/DisCatSharp/Entities/Voice/DiscordVoiceState.cs
@@ -1,215 +1,215 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a Discord voice state.
/// </summary>
public class DiscordVoiceState
{
/// <summary>
/// Gets the discord client.
/// </summary>
internal DiscordClient Discord { get; set; }
/// <summary>
/// Gets ID of the guild this voice state is associated with.
/// </summary>
[JsonProperty("guild_id", NullValueHandling = NullValueHandling.Ignore)]
internal ulong? GuildId { get; set; }
/// <summary>
/// Gets the guild associated with this voice state.
/// </summary>
[JsonIgnore]
public DiscordGuild Guild
=> this.GuildId != null ? this.Discord.Guilds[this.GuildId.Value] : this.Channel?.Guild;
/// <summary>
/// Gets ID of the channel this user is connected to.
/// </summary>
[JsonProperty("channel_id", NullValueHandling = NullValueHandling.Include)]
internal ulong? ChannelId { get; set; }
/// <summary>
/// Gets the channel this user is connected to.
/// </summary>
[JsonIgnore]
public DiscordChannel Channel
=> this.ChannelId != null && this.ChannelId.Value != 0 ? this.Discord.InternalGetCachedChannel(this.ChannelId.Value) : null;
/// <summary>
/// Gets ID of the user to which this voice state belongs.
/// </summary>
[JsonProperty("user_id", NullValueHandling = NullValueHandling.Ignore)]
internal ulong UserId { get; set; }
/// <summary>
/// Gets the user associated with this voice state.
/// <para>This can be cast to a <see cref="DisCatSharp.Entities.DiscordMember"/> if this voice state was in a guild.</para>
/// </summary>
[JsonIgnore]
public DiscordUser User
{
get
{
var usr = null as DiscordUser;
if (this.Guild != null)
usr = this.Guild.MembersInternal.TryGetValue(this.UserId, out var member) ? member : null;
if (usr == null)
usr = this.Discord.GetCachedOrEmptyUserInternal(this.UserId);
return usr;
}
}
/// <summary>
/// Gets ID of the session of this voice state.
/// </summary>
[JsonProperty("session_id", NullValueHandling = NullValueHandling.Ignore)]
internal string SessionId { get; set; }
/// <summary>
/// Gets whether this user is deafened.
/// </summary>
[JsonProperty("deaf", NullValueHandling = NullValueHandling.Ignore)]
public bool IsServerDeafened { get; internal set; }
/// <summary>
/// Gets whether this user is muted.
/// </summary>
[JsonProperty("mute", NullValueHandling = NullValueHandling.Ignore)]
public bool IsServerMuted { get; internal set; }
/// <summary>
/// Gets whether this user is locally deafened.
/// </summary>
[JsonProperty("self_deaf", NullValueHandling = NullValueHandling.Ignore)]
public bool IsSelfDeafened { get; internal set; }
/// <summary>
/// Gets whether this user is locally muted.
/// </summary>
[JsonProperty("self_mute", NullValueHandling = NullValueHandling.Ignore)]
public bool IsSelfMuted { get; internal set; }
/// <summary>
/// Gets whether this user's camera is enabled.
/// </summary>
[JsonProperty("self_video", NullValueHandling = NullValueHandling.Ignore)]
public bool IsSelfVideo { get; internal set; }
/// <summary>
/// Gets whether this user is using the Go Live feature.
/// </summary>
[JsonProperty("self_stream", NullValueHandling = NullValueHandling.Ignore)]
public bool IsSelfStream { get; internal set; }
/// <summary>
/// Gets whether the current user has suppressed this user.
/// </summary>
[JsonProperty("suppress", NullValueHandling = NullValueHandling.Ignore)]
public bool IsSuppressed { get; internal set; }
/// <summary>
/// Gets the time at which this user requested to speak.
/// </summary>
[JsonProperty("request_to_speak_timestamp", NullValueHandling = NullValueHandling.Ignore)]
internal DateTimeOffset? RequestToSpeakTimestamp { get; set; }
/// <summary>
/// Gets the member this voice state belongs to.
/// </summary>
[JsonIgnore]
public DiscordMember Member
=> this.Guild.Members.TryGetValue(this.TransportMember.User.Id, out var member) ? member : new DiscordMember(this.TransportMember) { Discord = this.Discord };
/// <summary>
/// Gets the transport member.
/// </summary>
[JsonProperty("member", NullValueHandling = NullValueHandling.Ignore)]
internal TransportMember TransportMember { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordVoiceState"/> class.
/// </summary>
internal DiscordVoiceState()
{ }
// copy constructor for reduced boilerplate
/// <summary>
/// Initializes a new instance of the <see cref="DiscordVoiceState"/> class.
/// </summary>
/// <param name="other">The other.</param>
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;
}
/// <summary>
/// Initializes a new instance of the <see cref="DiscordVoiceState"/> class.
/// </summary>
/// <param name="m">The m.</param>
internal DiscordVoiceState(DiscordMember m)
{
this.Discord = m.Discord as DiscordClient;
this.UserId = m.Id;
this.ChannelId = 0;
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
}
/// <summary>
/// Gets a readable voice state string.
/// </summary>
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 77f37a54f..403882156 100644
--- a/DisCatSharp/Entities/Webhook/DiscordWebhook.cs
+++ b/DisCatSharp/Entities/Webhook/DiscordWebhook.cs
@@ -1,316 +1,316 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Exceptions;
using DisCatSharp.Net;
using Newtonsoft.Json;
namespace DisCatSharp.Entities;
/// <summary>
/// Represents information about a Discord webhook.
/// </summary>
public class DiscordWebhook : SnowflakeObject, IEquatable<DiscordWebhook>
{
/// <summary>
/// Gets the api client.
/// </summary>
internal DiscordApiClient ApiClient { get; set; }
/// <summary>
/// Gets the id of the guild this webhook belongs to.
/// </summary>
[JsonProperty("guild_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong GuildId { get; internal set; }
/// <summary>
/// Gets the ID of the channel this webhook belongs to.
/// </summary>
[JsonProperty("channel_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong ChannelId { get; internal set; }
/// <summary>
/// Gets the user this webhook was created by.
/// </summary>
[JsonProperty("user", NullValueHandling = NullValueHandling.Ignore)]
public DiscordUser User { get; internal set; }
/// <summary>
/// Gets the default name of this webhook.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; internal set; }
/// <summary>
/// Gets hash of the default avatar for this webhook.
/// </summary>
[JsonProperty("avatar", NullValueHandling = NullValueHandling.Ignore)]
internal string AvatarHash { get; set; }
/// <summary>
/// Gets the partial source guild for this webhook (For Channel Follower Webhooks).
/// </summary>
[JsonProperty("source_guild", NullValueHandling = NullValueHandling.Ignore)]
public DiscordGuild SourceGuild { get; set; }
/// <summary>
/// Gets the partial source channel for this webhook (For Channel Follower Webhooks).
/// </summary>
[JsonProperty("source_channel", NullValueHandling = NullValueHandling.Ignore)]
public DiscordChannel SourceChannel { get; set; }
/// <summary>
/// Gets the url used for executing the webhook.
/// </summary>
[JsonProperty("url", NullValueHandling = NullValueHandling.Ignore)]
public string Url { get; set; }
/// <summary>
/// Gets the default avatar url for this webhook.
/// </summary>
public string AvatarUrl
=> !string.IsNullOrWhiteSpace(this.AvatarHash) ? $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.AVATARS}/{this.Id}/{this.AvatarHash}.png?size=1024" : null;
/// <summary>
/// Gets the secure token of this webhook.
/// </summary>
[JsonProperty("token", NullValueHandling = NullValueHandling.Ignore)]
public string Token { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordWebhook"/> class.
/// </summary>
internal DiscordWebhook()
{ }
/// <summary>
/// Modifies this webhook.
/// </summary>
/// <param name="name">New default name for this webhook.</param>
/// <param name="avatar">New avatar for this webhook.</param>
/// <param name="channelId">The new channel id to move the webhook to.</param>
/// <param name="reason">Reason for audit logs.</param>
/// <returns>The modified webhook.</returns>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageWebhooks"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the webhook does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordWebhook> ModifyAsync(string name = null, Optional<Stream> avatar = default, ulong? channelId = null, string reason = null)
{
var avatarb64 = ImageTool.Base64FromStream(avatar);
var newChannelId = channelId ?? this.ChannelId;
return this.Discord.ApiClient.ModifyWebhookAsync(this.Id, newChannelId, name, avatarb64, reason);
}
/// <summary>
/// Gets a previously-sent webhook message.
/// </summary>
/// <exception cref="NotFoundException">Thrown when the webhook does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordMessage> GetMessageAsync(ulong messageId)
=> (this.Discord?.ApiClient ?? this.ApiClient).GetWebhookMessageAsync(this.Id, this.Token, messageId);
/// <summary>
/// Tries to get a previously-sent webhook message.
/// </summary>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public async Task<DiscordMessage?> TryGetMessageAsync(ulong messageId)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
try
{
return await this.GetMessageAsync(messageId).ConfigureAwait(false);
}
catch (NotFoundException)
{
return null;
}
}
/// <summary>
/// Gets a previously-sent webhook message.
/// </summary>
/// <exception cref="NotFoundException">Thrown when the webhook does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordMessage> GetMessageAsync(ulong messageId, ulong threadId)
=> (this.Discord?.ApiClient ?? this.ApiClient).GetWebhookMessageAsync(this.Id, this.Token, messageId, threadId);
/// <summary>
/// Tries to get a previously-sent webhook message.
/// </summary>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordMessage> TryGetMessageAsync(ulong messageId, ulong threadId)
{
try
{
return await this.GetMessageAsync(messageId, threadId).ConfigureAwait(false);
}
catch (NotFoundException)
{
return null;
}
}
/// <summary>
/// Permanently deletes this webhook.
/// </summary>
/// <exception cref="UnauthorizedException">Thrown when the client does not have the <see cref="Permissions.ManageWebhooks"/> permission.</exception>
/// <exception cref="NotFoundException">Thrown when the webhook does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task DeleteAsync()
=> this.Discord.ApiClient.DeleteWebhookAsync(this.Id, this.Token);
/// <summary>
/// Executes this webhook with the given <see cref="DiscordWebhookBuilder"/>.
/// </summary>
/// <param name="builder">Webhook builder filled with data to send.</param>
/// <param name="threadId">Target thread id (Optional). Defaults to null.</param>
/// <exception cref="NotFoundException">Thrown when the webhook does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task<DiscordMessage> ExecuteAsync(DiscordWebhookBuilder builder, string threadId = null)
=> (this.Discord?.ApiClient ?? this.ApiClient).ExecuteWebhookAsync(this.Id, this.Token, builder, threadId);
/// <summary>
/// Executes this webhook in Slack compatibility mode.
/// </summary>
/// <param name="json">JSON containing Slack-compatible payload for this webhook.</param>
/// <param name="threadId">Target thread id (Optional). Defaults to null.</param>
/// <exception cref="NotFoundException">Thrown when the webhook does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task ExecuteSlackAsync(string json, string threadId = null)
=> (this.Discord?.ApiClient ?? this.ApiClient).ExecuteWebhookSlackAsync(this.Id, this.Token, json, threadId);
/// <summary>
/// Executes this webhook in GitHub compatibility mode.
/// </summary>
/// <param name="json">JSON containing GitHub-compatible payload for this webhook.</param>
/// <param name="threadId">Target thread id (Optional). Defaults to null.</param>
/// <exception cref="NotFoundException">Thrown when the webhook does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task ExecuteGithubAsync(string json, string threadId = null)
=> (this.Discord?.ApiClient ?? this.ApiClient).ExecuteWebhookGithubAsync(this.Id, this.Token, json, threadId);
/// <summary>
/// Edits a previously-sent webhook message.
/// </summary>
/// <param name="messageId">The id of the message to edit.</param>
/// <param name="builder">The builder of the message to edit.</param>
/// <param name="threadId">Target thread id (Optional). Defaults to null.</param>
/// <returns>The modified <see cref="DiscordMessage"/></returns>
/// <exception cref="NotFoundException">Thrown when the webhook does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public async Task<DiscordMessage> EditMessageAsync(ulong messageId, DiscordWebhookBuilder builder, string threadId = null)
{
builder.Validate(true);
if (builder.KeepAttachmentsInternal.HasValue && builder.KeepAttachmentsInternal.Value)
{
builder.AttachmentsInternal.AddRange(this.ApiClient.GetWebhookMessageAsync(this.Id, this.Token, messageId.ToString(), threadId).Result.Attachments);
}
else if (builder.KeepAttachmentsInternal.HasValue)
{
builder.AttachmentsInternal.Clear();
}
return await (this.Discord?.ApiClient ?? this.ApiClient).EditWebhookMessageAsync(this.Id, this.Token, messageId.ToString(), builder, threadId).ConfigureAwait(false);
}
/// <summary>
/// Deletes a message that was created by the webhook.
/// </summary>
/// <param name="messageId">The id of the message to delete</param>
/// <exception cref="NotFoundException">Thrown when the webhook does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task DeleteMessageAsync(ulong messageId)
=> (this.Discord?.ApiClient ?? this.ApiClient).DeleteWebhookMessageAsync(this.Id, this.Token, messageId);
/// <summary>
/// Deletes a message that was created by the webhook.
/// </summary>
/// <param name="messageId">The id of the message to delete</param>
/// <param name="threadId">Target thread id (Optional). Defaults to null.</param>
/// <exception cref="NotFoundException">Thrown when the webhook does not exist.</exception>
/// <exception cref="BadRequestException">Thrown when an invalid parameter was provided.</exception>
/// <exception cref="ServerErrorException">Thrown when Discord is unable to process the request.</exception>
public Task DeleteMessageAsync(ulong messageId, ulong threadId)
=> (this.Discord?.ApiClient ?? this.ApiClient).DeleteWebhookMessageAsync(this.Id, this.Token, messageId, threadId);
/// <summary>
/// Checks whether this <see cref="DiscordWebhook"/> is equal to another object.
/// </summary>
/// <param name="obj">Object to compare to.</param>
/// <returns>Whether the object is equal to this <see cref="DiscordWebhook"/>.</returns>
public override bool Equals(object obj) => this.Equals(obj as DiscordWebhook);
/// <summary>
/// Checks whether this <see cref="DiscordWebhook"/> is equal to another <see cref="DiscordWebhook"/>.
/// </summary>
/// <param name="e"><see cref="DiscordWebhook"/> to compare to.</param>
/// <returns>Whether the <see cref="DiscordWebhook"/> is equal to this <see cref="DiscordWebhook"/>.</returns>
public bool Equals(DiscordWebhook e) => e is not null && (ReferenceEquals(this, e) || this.Id == e.Id);
/// <summary>
/// Gets the hash code for this <see cref="DiscordWebhook"/>.
/// </summary>
/// <returns>The hash code for this <see cref="DiscordWebhook"/>.</returns>
public override int GetHashCode() => this.Id.GetHashCode();
/// <summary>
/// Gets whether the two <see cref="DiscordWebhook"/> objects are equal.
/// </summary>
/// <param name="e1">First webhook to compare.</param>
/// <param name="e2">Second webhook to compare.</param>
/// <returns>Whether the two webhooks are equal.</returns>
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);
}
/// <summary>
/// Gets whether the two <see cref="DiscordWebhook"/> objects are not equal.
/// </summary>
/// <param name="e1">First webhook to compare.</param>
/// <param name="e2">Second webhook to compare.</param>
/// <returns>Whether the two webhooks are not equal.</returns>
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 46eb7c9f5..39ca177cc 100644
--- a/DisCatSharp/Entities/Webhook/DiscordWebhookBuilder.cs
+++ b/DisCatSharp/Entities/Webhook/DiscordWebhookBuilder.cs
@@ -1,462 +1,462 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
using DisCatSharp.Enums;
namespace DisCatSharp.Entities;
/// <summary>
/// Constructs ready-to-send webhook requests.
/// </summary>
public sealed class DiscordWebhookBuilder
{
/// <summary>
/// Username to use for this webhook request.
/// </summary>
public Optional<string> Username { get; set; }
/// <summary>
/// Avatar url to use for this webhook request.
/// </summary>
public Optional<string> AvatarUrl { get; set; }
/// <summary>
/// Whether this webhook request is text-to-speech.
/// </summary>
public bool IsTts { get; set; }
/// <summary>
/// Message to send on this webhook request.
/// </summary>
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;
/// <summary>
/// Name of the new thread.
/// Only works if the webhook is send in a <see cref="ChannelType.Forum"/>.
/// </summary>
public string ThreadName { get; set; }
/// <summary>
/// Whether to keep previous attachments.
/// </summary>
internal bool? KeepAttachmentsInternal;
/// <summary>
/// Embeds to send on this webhook request.
/// </summary>
public IReadOnlyList<DiscordEmbed> Embeds => this._embeds;
private readonly List<DiscordEmbed> _embeds = new();
/// <summary>
/// Files to send on this webhook request.
/// </summary>
public IReadOnlyList<DiscordMessageFile> Files => this._files;
private readonly List<DiscordMessageFile> _files = new();
/// <summary>
/// Mentions to send on this webhook request.
/// </summary>
public IReadOnlyList<IMention> Mentions => this._mentions;
private readonly List<IMention> _mentions = new();
/// <summary>
/// Gets the components.
/// </summary>
public IReadOnlyList<DiscordActionRowComponent> Components => this._components;
private readonly List<DiscordActionRowComponent> _components = new();
/// <summary>
/// Attachments to keep on this webhook request.
/// </summary>
public IReadOnlyList<DiscordAttachment> Attachments => this.AttachmentsInternal;
internal List<DiscordAttachment> AttachmentsInternal = new();
/// <summary>
/// Constructs a new empty webhook request builder.
/// </summary>
public DiscordWebhookBuilder() { } // I still see no point in initializing collections with empty collections. //
/// <summary>
/// Adds a row of components to the builder, up to 5 components per row, and up to 5 rows per message.
/// </summary>
/// <param name="components">The components to add to the builder.</param>
/// <returns>The current builder to be chained.</returns>
/// <exception cref="ArgumentOutOfRangeException">No components were passed.</exception>
public DiscordWebhookBuilder AddComponents(params DiscordComponent[] components)
=> this.AddComponents((IEnumerable<DiscordComponent>)components);
/// <summary>
/// Appends several rows of components to the builder
/// </summary>
/// <param name="components">The rows of components to add, holding up to five each.</param>
/// <returns></returns>
public DiscordWebhookBuilder AddComponents(IEnumerable<DiscordActionRowComponent> 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;
}
/// <summary>
/// Adds a row of components to the builder, up to 5 components per row, and up to 5 rows per message.
/// </summary>
/// <param name="components">The components to add to the builder.</param>
/// <returns>The current builder to be chained.</returns>
/// <exception cref="ArgumentOutOfRangeException">No components were passed.</exception>
public DiscordWebhookBuilder AddComponents(IEnumerable<DiscordComponent> 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;
}
/// <summary>
/// Sets the username for this webhook builder.
/// </summary>
/// <param name="username">Username of the webhook</param>
public DiscordWebhookBuilder WithUsername(string username)
{
this.Username = username;
return this;
}
/// <summary>
/// Sets the avatar of this webhook builder from its url.
/// </summary>
/// <param name="avatarUrl">Avatar url of the webhook</param>
public DiscordWebhookBuilder WithAvatarUrl(string avatarUrl)
{
this.AvatarUrl = avatarUrl;
return this;
}
/// <summary>
/// Indicates if the webhook must use text-to-speech.
/// </summary>
/// <param name="tts">Text-to-speech</param>
public DiscordWebhookBuilder WithTts(bool tts)
{
this.IsTts = tts;
return this;
}
/// <summary>
/// Sets the message to send at the execution of the webhook.
/// </summary>
/// <param name="content">Message to send.</param>
public DiscordWebhookBuilder WithContent(string content)
{
this.Content = content;
return this;
}
/// <summary>
/// Sets the thread name to create at the execution of the webhook.
/// Only works for <see cref="ChannelType.Forum"/>.
/// </summary>
/// <param name="name">The thread name.</param>
public DiscordWebhookBuilder WithThreadName(string name)
{
this.ThreadName = name;
return this;
}
/// <summary>
/// Adds an embed to send at the execution of the webhook.
/// </summary>
/// <param name="embed">Embed to add.</param>
public DiscordWebhookBuilder AddEmbed(DiscordEmbed embed)
{
if (embed != null)
this._embeds.Add(embed);
return this;
}
/// <summary>
/// Adds the given embeds to send at the execution of the webhook.
/// </summary>
/// <param name="embeds">Embeds to add.</param>
public DiscordWebhookBuilder AddEmbeds(IEnumerable<DiscordEmbed> embeds)
{
this._embeds.AddRange(embeds);
return this;
}
/// <summary>
/// Adds a file to send at the execution of the webhook.
/// </summary>
/// <param name="filename">Name of the file.</param>
/// <param name="data">File data.</param>
/// <param name="resetStreamPosition">Tells the API Client to reset the stream position to what it was after the file is sent.</param>
/// <param name="description">Description of the file.</param>
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;
}
/// <summary>
/// Sets if the message has files to be sent.
/// </summary>
/// <param name="stream">The Stream to the file.</param>
/// <param name="resetStreamPosition">Tells the API Client to reset the stream position to what it was after the file is sent.</param>
/// <param name="description">Description of the file.</param>
/// <returns></returns>
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;
}
/// <summary>
/// Adds the given files to send at the execution of the webhook.
/// </summary>
/// <param name="files">Dictionary of file name and file data.</param>
/// <param name="resetStreamPosition">Tells the API Client to reset the stream position to what it was after the file is sent.</param>
public DiscordWebhookBuilder AddFiles(Dictionary<string, Stream> 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;
}
/// <summary>
/// Modifies the given attachments on edit.
/// </summary>
/// <param name="attachments">Attachments to edit.</param>
/// <returns></returns>
public DiscordWebhookBuilder ModifyAttachments(IEnumerable<DiscordAttachment> attachments)
{
this.AttachmentsInternal.AddRange(attachments);
return this;
}
/// <summary>
/// Whether to keep the message attachments, if new ones are added.
/// </summary>
/// <returns></returns>
public DiscordWebhookBuilder KeepAttachments(bool keep)
{
this.KeepAttachmentsInternal = keep;
return this;
}
/// <summary>
/// Adds the mention to the mentions to parse, etc. at the execution of the webhook.
/// </summary>
/// <param name="mention">Mention to add.</param>
public DiscordWebhookBuilder AddMention(IMention mention)
{
this._mentions.Add(mention);
return this;
}
/// <summary>
/// Adds the mentions to the mentions to parse, etc. at the execution of the webhook.
/// </summary>
/// <param name="mentions">Mentions to add.</param>
public DiscordWebhookBuilder AddMentions(IEnumerable<IMention> mentions)
{
this._mentions.AddRange(mentions);
return this;
}
/// <summary>
/// Executes a webhook.
/// </summary>
/// <param name="webhook">The webhook that should be executed.</param>
/// <returns>The message sent</returns>
public async Task<DiscordMessage> SendAsync(DiscordWebhook webhook) => await webhook.ExecuteAsync(this).ConfigureAwait(false);
/// <summary>
/// Executes a webhook.
/// </summary>
/// <param name="webhook">The webhook that should be executed.</param>
/// <param name="threadId">Target thread id.</param>
/// <returns>The message sent</returns>
public async Task<DiscordMessage> SendAsync(DiscordWebhook webhook, ulong threadId) => await webhook.ExecuteAsync(this, threadId.ToString()).ConfigureAwait(false);
/// <summary>
/// Sends the modified webhook message.
/// </summary>
/// <param name="webhook">The webhook that should be executed.</param>
/// <param name="message">The message to modify.</param>
/// <returns>The modified message</returns>
public async Task<DiscordMessage> ModifyAsync(DiscordWebhook webhook, DiscordMessage message) => await this.ModifyAsync(webhook, message.Id).ConfigureAwait(false);
/// <summary>
/// Sends the modified webhook message.
/// </summary>
/// <param name="webhook">The webhook that should be executed.</param>
/// <param name="messageId">The id of the message to modify.</param>
/// <returns>The modified message</returns>
public async Task<DiscordMessage> ModifyAsync(DiscordWebhook webhook, ulong messageId) => await webhook.EditMessageAsync(messageId, this).ConfigureAwait(false);
/// <summary>
/// Sends the modified webhook message.
/// </summary>
/// <param name="webhook">The webhook that should be executed.</param>
/// <param name="message">The message to modify.</param>
/// <param name="thread">Target thread.</param>
/// <returns>The modified message</returns>
public async Task<DiscordMessage> ModifyAsync(DiscordWebhook webhook, DiscordMessage message, DiscordThreadChannel thread) => await this.ModifyAsync(webhook, message.Id, thread.Id).ConfigureAwait(false);
/// <summary>
/// Sends the modified webhook message.
/// </summary>
/// <param name="webhook">The webhook that should be executed.</param>
/// <param name="messageId">The id of the message to modify.</param>
/// <param name="threadId">Target thread id.</param>
/// <returns>The modified message</returns>
public async Task<DiscordMessage> ModifyAsync(DiscordWebhook webhook, ulong messageId, ulong threadId) => await webhook.EditMessageAsync(messageId, this, threadId.ToString()).ConfigureAwait(false);
/// <summary>
/// Clears all message components on this builder.
/// </summary>
public void ClearComponents()
=> this._components.Clear();
/// <summary>
/// Allows for clearing the Webhook Builder so that it can be used again to send a new message.
/// </summary>
public void Clear()
{
this.Content = "";
this._embeds.Clear();
this.IsTts = false;
this._mentions.Clear();
this._files.Clear();
this.AttachmentsInternal.Clear();
this._components.Clear();
this.KeepAttachmentsInternal = false;
this.ThreadName = null;
}
/// <summary>
/// Does the validation before we send a the Create/Modify request.
/// </summary>
/// <param name="isModify">Tells the method to perform the Modify Validation or Create Validation.</param>
/// <param name="isFollowup">Tells the method to perform the follow up message validation.</param>
/// <param name="isInteractionResponse">Tells the method to perform the interaction response validation.</param>
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() && !this.Components.Any())
throw new ArgumentException("You must specify content, an embed, a component, or at least one file.");
}
}
}
diff --git a/DisCatSharp/Enums/Application/ApplicationCommandOptionType.cs b/DisCatSharp/Enums/Application/ApplicationCommandOptionType.cs
index 2099667d3..d5d35ef11 100644
--- a/DisCatSharp/Enums/Application/ApplicationCommandOptionType.cs
+++ b/DisCatSharp/Enums/Application/ApplicationCommandOptionType.cs
@@ -1,84 +1,84 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents the type of parameter when invoking an interaction.
/// </summary>
public enum ApplicationCommandOptionType
{
/// <summary>
/// Whether this parameter is another subcommand.
/// </summary>
SubCommand = 1,
/// <summary>
/// Whether this parameter is apart of a subcommand group.
/// </summary>
SubCommandGroup = 2,
/// <summary>
/// Whether this parameter is a string.
/// </summary>
String = 3,
/// <summary>
/// Whether this parameter is an integer.
/// </summary>
Integer = 4,
/// <summary>
/// Whether this parameter is a boolean.
/// </summary>
Boolean = 5,
/// <summary>
/// Whether this parameter is a Discord user.
/// </summary>
User = 6,
/// <summary>
/// Whether this parameter is a Discord channel.
/// </summary>
Channel = 7,
/// <summary>
/// Whether this parameter is a Discord role.
/// </summary>
Role = 8,
/// <summary>
/// Whether this parameter is a mentionable.
/// </summary>
Mentionable = 9,
/// <summary>
/// Whether this parameter is a number.
/// </summary>
Number = 10,
/// <summary>
/// Whether this parameter is a attachment.
/// </summary>
Attachment = 11
}
diff --git a/DisCatSharp/Enums/Application/ApplicationCommandPermissionType.cs b/DisCatSharp/Enums/Application/ApplicationCommandPermissionType.cs
index 7ad0a1dad..5ef2fec00 100644
--- a/DisCatSharp/Enums/Application/ApplicationCommandPermissionType.cs
+++ b/DisCatSharp/Enums/Application/ApplicationCommandPermissionType.cs
@@ -1,47 +1,47 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents the type of the application command permission.
/// </summary>
[Flags]
public enum ApplicationCommandPermissionType
{
/// <summary>
/// The permission is bound to a role.
/// </summary>
Role = 1,
/// <summary>
/// The permission is bound to a user.
/// </summary>
User = 2,
/// <summary>
/// The permission is bound to a channel.
/// </summary>
Channel = 3
}
diff --git a/DisCatSharp/Enums/Application/ApplicationCommandType.cs b/DisCatSharp/Enums/Application/ApplicationCommandType.cs
index b082f3a57..a7f20e435 100644
--- a/DisCatSharp/Enums/Application/ApplicationCommandType.cs
+++ b/DisCatSharp/Enums/Application/ApplicationCommandType.cs
@@ -1,54 +1,54 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents the type of an <see cref="DisCatSharp.Entities.DiscordApplicationCommand"/>.
/// </summary>
public enum ApplicationCommandType
{
/// <summary>
/// This command is registered as a slash-command, aka "Chat Input".
/// </summary>
ChatInput = 1,
/// <summary>
/// This command is registered as a user context menu, and is applicable when interacting a user.
/// </summary>
User = 2,
/// <summary>
/// This command is registered as a message context menu, and is applicable when interacting with a message.
/// </summary>
Message = 3,
/// <summary>
/// Inbound only: An auto-complete option is being interacted with.
/// </summary>
AutoCompleteRequest = 4,
/// <summary>
/// Inbound only: A modal was submitted.
/// </summary>
ModalSubmit = 5
}
diff --git a/DisCatSharp/Enums/Application/ApplicationFlags.cs b/DisCatSharp/Enums/Application/ApplicationFlags.cs
index eeae30b2b..6ba03976a 100644
--- a/DisCatSharp/Enums/Application/ApplicationFlags.cs
+++ b/DisCatSharp/Enums/Application/ApplicationFlags.cs
@@ -1,138 +1,138 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents additional details of an application.
/// </summary>
[Flags]
public enum ApplicationFlags : long
{
/// <summary>
/// The application is embedded and can be used by users.
/// This was introduced to avoid users using in-dev apps.
/// </summary>
EmbeddedReleased = 1L << 1,
/// <summary>
/// The application is a managed emoji.
/// </summary>
ManagedEmoji = 1L << 2,
/// <summary>
/// Unknown, relates to in app purchase.
/// </summary>
EmbeddedIap = 1L << 3,
/// <summary>
/// The application can create group dms.
/// </summary>
GroupDmCreate = 1L << 4,
/// <summary>
/// Allows the application to access the local RPC server.
/// </summary>
RpcPrivateBeta = 1L << 6,
/// <summary>
/// Allows the application to create activity assets.
/// </summary>
AllowAssets = 1L<<8,
/// <summary>
/// Allows the application to enable activity spectating.
/// </summary>
AllowActivityActionSpectate = 1L<<9,
/// <summary>
/// Allows the application to enable join requests for activities.
/// </summary>
AllowActivityActionJoinRequest = 1L<<10,
/// <summary>
/// The application has connected to RPC.
/// </summary>
RpcHasConnected = 1L << 11,
/// <summary>
/// The application can track presence data.
/// </summary>
GatewayPresence = 1L << 12,
/// <summary>
/// The application can track presence data (limited).
/// </summary>
GatewayPresenceLimited = 1L << 13,
/// <summary>
/// The application can track guild members.
/// </summary>
GatewayGuildMembers = 1L << 14,
/// <summary>
/// The application can track guild members (limited).
/// </summary>
GatewayGuildMembersLimited = 1L << 15,
/// <summary>
/// The application can track pending guild member verifications (limited).
/// </summary>
VerificationPendingGuildLimit = 1L << 16,
/// <summary>
/// The application is embedded.
/// </summary>
Embedded = 1L << 17,
/// <summary>
/// The application can track message content.
/// </summary>
GatewayMessageContent = 1L << 18,
/// <summary>
/// The application can track message content (limited).
/// </summary>
GatewayMessageContentLimited = 1L << 19,
/// <summary>
/// Related to embedded applications.
/// </summary>
EmbeddedFirstParty = 1L << 20,
/// <summary>
/// To be datamined.
/// </summary>
UnknownFlag = 1L << 21,
/// <summary>
/// The application has registered global application commands.
/// </summary>
ApplicationCommandBadge = 1L << 23,
/// <summary>
/// Indicates if an app is considered active. This means that it has had any global command executed in the past 30 days.
/// </summary>
Active = 1L << 24
}
diff --git a/DisCatSharp/Enums/Application/ApplicationRoleConnectionMetadataType.cs b/DisCatSharp/Enums/Application/ApplicationRoleConnectionMetadataType.cs
index e40607699..75e799f97 100644
--- a/DisCatSharp/Enums/Application/ApplicationRoleConnectionMetadataType.cs
+++ b/DisCatSharp/Enums/Application/ApplicationRoleConnectionMetadataType.cs
@@ -1,69 +1,69 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents the type of an <see cref="DisCatSharp.Entities.DiscordApplicationRoleConnectionMetadata"/>.
/// </summary>
public enum ApplicationRoleConnectionMetadataType
{
/// <summary>
/// The metadata value (`integer`) is less than or equal to the guild's configured value (`integer`).
/// </summary>
IntegerLessThanOrÈqual = 1,
/// <summary>
/// The metadata value (`integer`) is greater than or equal to the guild's configured value (`integer`).
/// </summary>
IntegerGreaterThanOrÈqual = 2,
/// <summary>
/// The metadata value (`integer`) is equal to the guild's configured value (`integer`).
/// </summary>
IntegerEqual = 3,
/// <summary>
/// The metadata value (`integer`) is not equal to the guild's configured value (`integer`).
/// </summary>
IntegerNotEqual = 4,
/// <summary>
/// The metadata value (`ISO8601 string`) is less than or equal to the guild's configured value (`integer`; `days before current date`).
/// </summary>
DatetimeLessThanOrÈqual = 5,
/// <summary>
/// The metadata value (`ISO8601 string`) is greater than or equal to the guild's configured value (`integer`; `days before current date`).
/// </summary>
DatetimeGreaterThanOrÈqual = 6,
/// <summary>
/// The metadata value (`integer`) is equal to the guild's configured value (`integer`; `1`).
/// </summary>
BooleanEqual = 7,
/// <summary>
/// The metadata value (`integer`) is not equal to the guild's configured value (`integer`; `1`).
/// </summary>
BooleanNotEqual = 8
}
diff --git a/DisCatSharp/Enums/Channel/ChannelFlags.cs b/DisCatSharp/Enums/Channel/ChannelFlags.cs
index 923a7e883..a9f15f0f0 100644
--- a/DisCatSharp/Enums/Channel/ChannelFlags.cs
+++ b/DisCatSharp/Enums/Channel/ChannelFlags.cs
@@ -1,56 +1,56 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
namespace DisCatSharp.Enums;
/// <summary>
/// Represents a channel's flags.
/// </summary>
public enum ChannelFlags : int
{
/// <summary>
/// Indicates that this channel is removed from the guilds home feed / from highlights.
/// Applicable for <see cref="ChannelType"/> Text, Forum and News.
/// </summary>
RemovedFromHome = 1 << 0,
RemovedFromHighlights = RemovedFromHome,
/// <summary>
/// Indicates that this thread is pinned to the top of its parent forum channel.
/// Forum channel thread only.
/// </summary>
Pinned = 1 << 1,
/// <summary>
/// Indicates that this channel is removed from the active now within the guilds home feed.
/// Applicable for <see cref="ChannelType"/> Text, News, Thread, Forum, Stage and Voice.
/// </summary>
RemovedFromActiveNow = 1 << 2,
/// <summary>
/// Indicates that the channel requires users to select at least one <see cref="ForumPostTag"/>.
/// Only applicable for <see cref="ChannelType.Forum"/>.
/// </summary>
RequireTags = 1<<4
}
diff --git a/DisCatSharp/Enums/Channel/ChannelType.cs b/DisCatSharp/Enums/Channel/ChannelType.cs
index df8de5367..33fc137d4 100644
--- a/DisCatSharp/Enums/Channel/ChannelType.cs
+++ b/DisCatSharp/Enums/Channel/ChannelType.cs
@@ -1,101 +1,101 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents a channel's type.
/// </summary>
public enum ChannelType : int
{
/// <summary>
/// Indicates that this is a text channel.
/// </summary>
Text = 0,
/// <summary>
/// Indicates that this is a private channel.
/// </summary>
Private = 1,
/// <summary>
/// Indicates that this is a voice channel.
/// </summary>
Voice = 2,
/// <summary>
/// Indicates that this is a group direct message channel.
/// </summary>
Group = 3,
/// <summary>
/// Indicates that this is a channel category.
/// </summary>
Category = 4,
/// <summary>
/// Indicates that this is a news channel.
/// </summary>
News = 5,
/// <summary>
/// Indicates that this is a store channel.
/// </summary>
Store = 6,
/// <summary>
/// Indicates that this is a temporary sub-channel within a news channel.
/// </summary>
NewsThread = 10,
/// <summary>
/// Indicates that this is a temporary sub-channel within a text channel.
/// </summary>
PublicThread = 11,
/// <summary>
/// Indicates that this is a temporary sub-channel within a text channel that is only viewable
/// by those invited and those with the MANAGE_THREADS permission.
/// </summary>
PrivateThread = 12,
/// <summary>
/// Indicates that this is a stage channel.
/// </summary>
Stage = 13,
/// <summary>
/// Indicates that this is a guild directory channel.
/// This is used for hub guilds (feature for schools).
/// </summary>
GuildDirectory = 14,
/// <summary>
/// Indicates that this is a guild forum channel (Threads only channel).
/// </summary>
Forum = 15,
/// <summary>
/// Indicates unknown channel type.
/// </summary>
Unknown = int.MaxValue
}
diff --git a/DisCatSharp/Enums/Channel/DirectoryCategory.cs b/DisCatSharp/Enums/Channel/DirectoryCategory.cs
index 9c16eef8c..abd9c7fb8 100644
--- a/DisCatSharp/Enums/Channel/DirectoryCategory.cs
+++ b/DisCatSharp/Enums/Channel/DirectoryCategory.cs
@@ -1,59 +1,59 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents a directory entries primary category type.
/// </summary>
public enum DirectoryCategory : int
{
/// <summary>
/// Indicates that this entry falls under the category Clubs.
/// </summary>
Clubs = 1,
/// <summary>
/// Indicates that this entry falls under the category Classes.
/// </summary>
Classes = 2,
/// <summary>
/// Indicates that this entry falls under the category Social and Study.
/// </summary>
SocialAndStudy = 3,
/// <summary>
/// Indicates that this entry falls under the category Majors and Subjects.
/// </summary>
MajorsAndSubjects = 4,
/// <summary>
/// Indicates that this entry falls under the category Miscellaneous.
/// </summary>
Miscellaneous = 5,
/// <summary>
/// Indicates unknown category type.
/// </summary>
Unknown = int.MaxValue
}
diff --git a/DisCatSharp/Enums/Channel/OverwriteType.cs b/DisCatSharp/Enums/Channel/OverwriteType.cs
index e60487e06..6321d1c90 100644
--- a/DisCatSharp/Enums/Channel/OverwriteType.cs
+++ b/DisCatSharp/Enums/Channel/OverwriteType.cs
@@ -1,39 +1,39 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents a channel permission overwrite's type.
/// </summary>
public enum OverwriteType : int
{
/// <summary>
/// Specifies that this overwrite applies to a role.
/// </summary>
Role = 0,
/// <summary>
/// Specifies that this overwrite applies to a member.
/// </summary>
Member = 1,
}
diff --git a/DisCatSharp/Enums/Channel/VideoQualityMode.cs b/DisCatSharp/Enums/Channel/VideoQualityMode.cs
index be9b6a1c1..6598678c2 100644
--- a/DisCatSharp/Enums/Channel/VideoQualityMode.cs
+++ b/DisCatSharp/Enums/Channel/VideoQualityMode.cs
@@ -1,39 +1,39 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents the video quality mode of a voice channel. This is applicable to voice channels only.
/// </summary>
public enum VideoQualityMode : int
{
/// <summary>
/// Indicates that the video quality is automatically chosen, or there is no value set.
/// </summary>
Auto = 1,
/// <summary>
/// Indicates that the video quality is 720p.
/// </summary>
Full = 2,
}
diff --git a/DisCatSharp/Enums/Discord/DiscordDomain.cs b/DisCatSharp/Enums/Discord/DiscordDomain.cs
index 09c24911b..499fed07f 100644
--- a/DisCatSharp/Enums/Discord/DiscordDomain.cs
+++ b/DisCatSharp/Enums/Discord/DiscordDomain.cs
@@ -1,268 +1,268 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Core Domains
/// </summary>
public enum CoreDomain
{
/// <summary>
/// dis.gd
/// </summary>
[DomainHelp("Marketing URL shortener", "dis.gd")]
DiscordMarketing = 1,
/// <summary>
/// discord.co
/// </summary>
[DomainHelp("Admin panel, internal tools", "discord.co")]
DiscordAdmin = 2,
/// <summary>
/// discord.com
/// </summary>
[DomainHelp("New app, marketing website, API host", "discord.com")]
Discord = 3,
/// <summary>
/// discord.design
/// </summary>
[DomainHelp("Dribbble profile shortlink", "discord.design")]
DiscordDesign = 4,
/// <summary>
/// discord.dev
/// </summary>
[DomainHelp("Developer site shortlinks", "discord.dev")]
DiscordDev = 5,
/// <summary>
/// discord.gg
/// </summary>
[DomainHelp("Invite shortlinks", "discord.gg")]
DiscordShortlink = 6,
/// <summary>
/// discord.gift
/// </summary>
[DomainHelp("Gift shortlinks", "discord.gift")]
DiscordGift = 7,
/// <summary>
/// discord.media
/// </summary>
[DomainHelp("Voice servers", "discord.media")]
DiscordMedia = 8,
/// <summary>
/// discord.new
/// </summary>
[DomainHelp("Template shortlinks", "discord.new")]
DiscordTemplate = 9,
/// <summary>
/// discord.store
/// </summary>
[DomainHelp("Merch store", "discord.store")]
DiscordMerch = 10,
/// <summary>
/// discord.tools
/// </summary>
[DomainHelp("Internal tools", "discord.tools")]
DiscordTools = 11,
/// <summary>
/// discordapp.com
/// </summary>
[DomainHelp("Old app, marketing website, and API; CDN", "discordapp.com")]
DiscordAppOld = 12,
/// <summary>
/// discordapp.net
/// </summary>
[DomainHelp("Media Proxy", "discordapp.net")]
DiscordAppMediaProxy = 13,
/// <summary>
/// discordmerch.com
/// </summary>
[DomainHelp("Merch store", "discordmerch.com")]
DiscordMerchOld = 14,
/// <summary>
/// discordpartygames.com
/// </summary>
[DomainHelp("Voice channel activity API host", "discordpartygames.com")]
DiscordActivityAlt = 15,
/// <summary>
/// discord-activities.com
/// </summary>
[DomainHelp("Voice channel activity API host", "discord-activities.com")]
DiscordActivityAlt2 = 16,
/// <summary>
/// discordsays.com
/// </summary>
[DomainHelp("Voice channel activity host", "discordsays.com")]
DiscordActivity = 17,
/// <summary>
/// discordstatus.com
/// </summary>
[DomainHelp("Status page", "discordstatus.com")]
DiscordStatus = 18,
/// <summary>
/// cdn.discordapp.com
/// </summary>
[DomainHelp("CDN", "cdn.discordapp.com")]
DiscordCdn = 19,
}
/// <summary>
/// Other Domains
/// </summary>
public enum OtherDomain
{
/// <summary>
/// airhorn.solutions
/// </summary>
[DomainHelp("API implementation example", "airhorn.solutions")]
Airhorn = 1,
/// <summary>
/// airhornbot.com
/// </summary>
[DomainHelp("API implementation example", "airhornbot.com")]
AirhornAlt = 2,
/// <summary>
/// bigbeans.solutions
/// </summary>
[DomainHelp("April Fools 2017", "bigbeans.solutions")]
AprilFools = 3,
/// <summary>
/// watchanimeattheoffice.com
/// </summary>
[DomainHelp("HypeSquad form placeholder/meme", "watchanimeattheoffice.com")]
HypeSquadMeme = 4
}
/// <summary>
/// Unused Domains
/// </summary>
public enum UnusedDomain
{
/// <summary>
/// discordapp.io
/// </summary>
[Obsolete("Not in use.", false)]
[DomainHelp("IO domain for discord", "discordapp.io")]
DiscordAppIo = 1,
/// <summary>
/// discordcdn.com
/// </summary>
[Obsolete("Not in use.", false)]
[DomainHelp("Alternative CDN domain", "discordcdn.com")]
DiscordCdnCom = 2
}
/// <summary>
/// Represents a discord domain.
/// </summary>
public static class DiscordDomain
{
/// <summary>
/// Gets a domain.
/// Valid types: <see cref="CoreDomain"/>, <see cref="OtherDomain"/> and <see cref="UnusedDomain"/>.
/// </summary>
/// <param name="domainEnum">The domain type.</param>
/// <returns>A DomainHelpAttribute.</returns>
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 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();
if (memberInfo != null)
{
var attribute = (DomainHelpAttribute)memberInfo.GetCustomAttributes(typeof(DomainHelpAttribute), false).FirstOrDefault();
return attribute;
}
return null;
}
}
/// <summary>
/// Defines a description and url for this domain.
/// </summary>
[AttributeUsage(AttributeTargets.All)]
public class DomainHelpAttribute : Attribute
{
/// <summary>
/// Gets the Description for this domain.
/// </summary>
public string Description { get; }
/// <summary>
/// Gets the Uri for this domain.
/// </summary>
public Uri Uri { get; }
/// <summary>
/// Gets the Domain for this domain.
/// </summary>
public string Domain { get; }
/// <summary>
/// Gets the Url for this domain.
/// </summary>
public string Url { get; }
/// <summary>
/// Defines a description and URIs for this domain.
/// </summary>
/// <param name="desc">Description for this domain.</param>
/// <param name="domain">Url for this domain.</param>
public DomainHelpAttribute(string desc, string domain)
{
this.Description = desc;
this.Domain = domain;
var url = $"https://{domain}";
this.Url = url;
this.Uri = new Uri(url);
}
}
diff --git a/DisCatSharp/Enums/Discord/DiscordShortlink.cs b/DisCatSharp/Enums/Discord/DiscordShortlink.cs
index c68dcdaa5..e50d7098e 100644
--- a/DisCatSharp/Enums/Discord/DiscordShortlink.cs
+++ b/DisCatSharp/Enums/Discord/DiscordShortlink.cs
@@ -1,39 +1,39 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Discord short links.
/// </summary>
public static class DiscordShortlink
{
public static readonly string Support = $"{DiscordDomain.GetDomain(CoreDomain.DiscordMarketing).Url}/support";
public static readonly string TrustAndSafety = $"{DiscordDomain.GetDomain(CoreDomain.DiscordMarketing).Url}/request";
public static readonly string Contact = $"{DiscordDomain.GetDomain(CoreDomain.DiscordMarketing).Url}/contact";
public static readonly string BugReport = $"{DiscordDomain.GetDomain(CoreDomain.DiscordMarketing).Url}/bugreport";
public static readonly string TranslationError = $"{DiscordDomain.GetDomain(CoreDomain.DiscordMarketing).Url}/lang-feedback";
public static readonly string Status = $"{DiscordDomain.GetDomain(CoreDomain.DiscordMarketing).Url}/status";
public static readonly string Terms = $"{DiscordDomain.GetDomain(CoreDomain.DiscordMarketing).Url}/terms";
public static readonly string Guidelines = $"{DiscordDomain.GetDomain(CoreDomain.DiscordMarketing).Url}/guidelines";
public static readonly string Moderation = $"{DiscordDomain.GetDomain(CoreDomain.DiscordMarketing).Url}/moderation";
}
diff --git a/DisCatSharp/Enums/DiscordEvent.cs b/DisCatSharp/Enums/DiscordEvent.cs
index d1a1825a4..cf5243f93 100644
--- a/DisCatSharp/Enums/DiscordEvent.cs
+++ b/DisCatSharp/Enums/DiscordEvent.cs
@@ -1,144 +1,144 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.
#nullable enable
using System;
namespace DisCatSharp;
/// <summary>
/// Methods marked with this attribute will be registered as event handling methods
/// if the associated type / an associated instance is being registered.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class EventAttribute : Attribute
{
internal readonly string? EventName;
public EventAttribute() { }
/// <param name="evtn"><para>The name of the event.</para>
/// <para>The attributed method's name will be used if null.</para></param>
public EventAttribute(DiscordEvent evtn)
{
this.EventName = evtn.ToString();
}
}
/// <summary>
/// Classes marked with this attribute will be considered for event handler registration from an assembly.
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class EventHandlerAttribute : Attribute { }
/// <summary>
/// All events available in <see cref="DiscordClient"/> for use with <see cref="EventAttribute"/>.
/// </summary>
public enum DiscordEvent
{
ApplicationCommandCreated,
ApplicationCommandDeleted,
ApplicationCommandPermissionsUpdated,
ApplicationCommandUpdated,
ChannelCreated,
ChannelDeleted,
ChannelPinsUpdated,
ChannelUpdated,
ClientErrored,
ComponentInteractionCreated,
ContextMenuInteractionCreated,
DmChannelDeleted,
EmbeddedActivityUpdated,
GuildApplicationCommandCountUpdated,
GuildAvailable,
GuildBanAdded,
GuildBanRemoved,
GuildCreated,
GuildDeleted,
GuildDownloadCompleted,
GuildEmojisUpdated,
GuildIntegrationCreated,
GuildIntegrationDeleted,
GuildIntegrationsUpdated,
GuildIntegrationUpdated,
GuildMemberAdded,
GuildMemberRemoved,
GuildMembersChunked,
GuildMemberTimeoutAdded,
GuildMemberTimeoutChanged,
GuildMemberTimeoutRemoved,
GuildMemberUpdated,
GuildRoleCreated,
GuildRoleDeleted,
GuildRoleUpdated,
GuildScheduledEventCreated,
GuildScheduledEventDeleted,
GuildScheduledEventUpdated,
GuildScheduledEventUserAdded,
GuildScheduledEventUserRemoved,
GuildStickersUpdated,
GuildUnavailable,
GuildUpdated,
Heartbeated,
InteractionCreated,
InviteCreated,
InviteDeleted,
MessageAcknowledged,
MessageCreated,
MessageDeleted,
MessageReactionAdded,
MessageReactionRemoved,
MessageReactionRemovedEmoji,
MessageReactionsCleared,
MessagesBulkDeleted,
MessageUpdated,
PayloadReceived,
PresenceUpdated,
RateLimitHit,
Ready,
Resumed,
SocketClosed,
SocketErrored,
SocketOpened,
StageInstanceCreated,
StageInstanceDeleted,
StageInstanceUpdated,
ThreadCreated,
ThreadDeleted,
ThreadListSynced,
ThreadMembersUpdated,
ThreadMemberUpdated,
ThreadUpdated,
TypingStarted,
UnknownEvent,
UserSettingsUpdated,
UserUpdated,
VoiceServerUpdated,
VoiceStateUpdated,
WebhooksUpdated,
Zombied,
AutomodRuleCreated,
AutomodRuleUpdated,
AutomodRuleDeleted,
AutomodActionExecuted
}
diff --git a/DisCatSharp/Enums/DiscordIntents.cs b/DisCatSharp/Enums/DiscordIntents.cs
index 8f302d094..054c20ead 100644
--- a/DisCatSharp/Enums/DiscordIntents.cs
+++ b/DisCatSharp/Enums/DiscordIntents.cs
@@ -1,233 +1,233 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a discord intent extensions.
/// </summary>
public static class DiscordIntentExtensions
{
/// <summary>
/// Calculates whether these intents have a certain intent.
/// </summary>
/// <param name="intents">The base intents.</param>
/// <param name="search">The intents to search for.</param>
/// <returns></returns>
public static bool HasIntent(this DiscordIntents intents, DiscordIntents search)
=> (intents & search) == search;
/// <summary>
/// Adds an intent to these intents.
/// </summary>
/// <param name="intents">The base intents.</param>
/// <param name="toAdd">The intents to add.</param>
/// <returns></returns>
public static DiscordIntents AddIntent(this DiscordIntents intents, DiscordIntents toAdd)
=> intents |= toAdd;
/// <summary>
/// Removes an intent from these intents.
/// </summary>
/// <param name="intents">The base intents.</param>
/// <param name="toRemove">The intents to remove.</param>
/// <returns></returns>
public static DiscordIntents RemoveIntent(this DiscordIntents intents, DiscordIntents toRemove)
=> intents &= ~toRemove;
/// <summary>
/// Whether it has all privileged intents.
/// </summary>
/// <param name="intents">The intents.</param>
internal static bool HasAllPrivilegedIntents(this DiscordIntents intents)
=> intents.HasIntent(DiscordIntents.GuildMembers | DiscordIntents.GuildPresences | DiscordIntents.MessageContent);
/// <summary>
/// Whether it has all v9 privileged intents.
/// </summary>
/// <param name="intents">The intents.</param>
internal static bool HasAllV9PrivilegedIntents(this DiscordIntents intents)
=> intents.HasIntent(DiscordIntents.GuildMembers | DiscordIntents.GuildPresences);
}
/// <summary>
/// Represents gateway intents to be specified for connecting to Discord.
/// </summary>
[Flags]
public enum DiscordIntents
{
/// <summary>
/// Whether to include general guild events. Note that you may receive empty message contents if you don't have the message content intent.
/// <para>These include <see cref="DiscordClient.GuildCreated"/>, <see cref="DiscordClient.GuildDeleted"/>, <see cref="DiscordClient.GuildAvailable"/>, <see cref="DiscordClient.GuildDownloadCompleted"/>,</para>
/// <para><see cref="DiscordClient.GuildRoleCreated"/>, <see cref="DiscordClient.GuildRoleUpdated"/>, <see cref="DiscordClient.GuildRoleDeleted"/>,</para>
/// <para><see cref="DiscordClient.ChannelCreated"/>, <see cref="DiscordClient.ChannelUpdated"/>, <see cref="DiscordClient.ChannelDeleted"/>, <see cref="DiscordClient.ChannelPinsUpdated"/>,</para>
/// <para><see cref="DiscordClient.StageInstanceCreated"/>, <see cref="DiscordClient.StageInstanceUpdated"/>, <see cref="DiscordClient.StageInstanceDeleted"/>,</para>
/// <para><see cref="DiscordClient.ThreadCreated"/>, <see cref="DiscordClient.ThreadUpdated"/>, <see cref="DiscordClient.ThreadDeleted"/>,</para>
/// <para><see cref="DiscordClient.ThreadListSynced"/>, <see cref="DiscordClient.ThreadMemberUpdated"/> and <see cref="DiscordClient.ThreadMembersUpdated"/>.</para>
/// </summary>
Guilds = 1 << 0,
/// <summary>
/// Whether to include guild member events.
/// <para>These include <see cref="DiscordClient.GuildMemberAdded"/>, <see cref="DiscordClient.GuildMemberUpdated"/>, <see cref="DiscordClient.GuildMemberRemoved"/> and <see cref="DiscordClient.ThreadMembersUpdated"/>.</para>
/// <para>This is a privileged intent, and must be enabled on the bot's developer page.</para>
/// </summary>
GuildMembers = 1 << 1,
/// <summary>
/// Whether to include guild ban events.
/// <para>These include <see cref="DiscordClient.GuildBanAdded"/>, <see cref="DiscordClient.GuildBanRemoved"/> and <see cref="DiscordClient.GuildAuditLogEntryCreated"/>.</para>
/// </summary>
GuildModeration = 1 << 2,
[Obsolete("Renamed to GuildModeration")]
GuildBans = GuildModeration,
/// <summary>
/// Whether to include guild emoji and sticker events.
/// <para>This includes <see cref="DiscordClient.GuildEmojisUpdated"/> and <see cref="DiscordClient.GuildStickersUpdated"/>.</para>
/// </summary>
GuildEmojisAndStickers = 1 << 3,
/// <summary>
/// Whether to include guild integration events.
/// <para>This includes <see cref="DiscordClient.GuildIntegrationsUpdated"/>.</para>
/// </summary>
GuildIntegrations = 1 << 4,
/// <summary>
/// Whether to include guild webhook events.
/// <para>This includes <see cref="DiscordClient.WebhooksUpdated"/>.</para>
/// </summary>
GuildWebhooks = 1 << 5,
/// <summary>
/// Whether to include guild invite events.
/// <para>These include <see cref="DiscordClient.InviteCreated"/> and <see cref="DiscordClient.InviteDeleted"/>.</para>
/// </summary>
GuildInvites = 1 << 6,
/// <summary>
/// Whether to include guild voice state events.
/// <para>This includes <see cref="DiscordClient.VoiceStateUpdated"/>.</para>
/// </summary>
GuildVoiceStates = 1 << 7,
/// <summary>
/// Whether to include guild presence events.
/// <para>This includes <see cref="DiscordClient.PresenceUpdated"/>.</para>
/// <para>This is a privileged intent, and must be enabled on the bot's developer page.</para>
/// </summary>
GuildPresences = 1 << 8,
/// <summary>
/// Whether to include guild message events. Note that you may receive empty contents if you don't have the message content intent.
/// You can enable it in the developer portal. If you have a verified bot, you might need to apply for the intent.
/// <para>These include <see cref="DiscordClient.MessageCreated"/>, <see cref="DiscordClient.MessageUpdated"/>, and <see cref="DiscordClient.MessageDeleted"/>.</para>
/// </summary>
GuildMessages = 1 << 9,
/// <summary>
/// Whether to include guild reaction events.
/// <para>These include <see cref="DiscordClient.MessageReactionAdded"/>, <see cref="DiscordClient.MessageReactionRemoved"/>, <see cref="DiscordClient.MessageReactionsCleared"/></para>
/// <para>and <see cref="DiscordClient.MessageReactionRemovedEmoji"/>.</para>
/// </summary>
GuildMessageReactions = 1 << 10,
/// <summary>
/// Whether to include guild typing events.
/// <para>These include <see cref="DiscordClient.TypingStarted"/>.</para>
/// </summary>
GuildMessageTyping = 1 << 11,
/// <summary>
/// Whether to include general direct message events.
/// <para>These include <see cref="DiscordClient.ChannelCreated"/>, <see cref="DiscordClient.MessageCreated"/>, <see cref="DiscordClient.MessageUpdated"/>,</para>
/// <para><see cref="DiscordClient.MessageDeleted"/> and <see cref="DiscordClient.ChannelPinsUpdated"/>.</para>
/// <para>These events only fire for DM channels.</para>
/// </summary>
DirectMessages = 1 << 12,
/// <summary>
/// Whether to include direct message reaction events.
/// <para>These include <see cref="DiscordClient.MessageReactionAdded"/>, <see cref="DiscordClient.MessageReactionRemoved"/>,</para>
/// <para><see cref="DiscordClient.MessageReactionsCleared"/> and <see cref="DiscordClient.MessageReactionRemovedEmoji"/>.</para>
/// <para>These events only fire for DM channels.</para>
/// </summary>
DirectMessageReactions = 1 << 13,
/// <summary>
/// Whether to include direct message typing events.
/// <para>This includes <see cref="DiscordClient.TypingStarted"/>.</para>
/// <para>This event only fires for DM channels.</para>
/// </summary>
DirectMessageTyping = 1 << 14,
/// <summary>
/// Whether to include the content of guild messages.
/// See https://support-dev.discord.com/hc/en-us/articles/4404772028055-Message-Content-Privileged-Intent-for-Verified-Bots for more information.
/// </summary>
MessageContent = 1 << 15,
/// <summary>
/// Whether to include guild scheduled event events.
/// <para>These include <see cref="DiscordClient.GuildScheduledEventCreated"/>, <see cref="DiscordClient.GuildScheduledEventUpdated"/>, <see cref="DiscordClient.GuildScheduledEventDeleted"/>,</para>
/// <para><see cref="DiscordClient.GuildScheduledEventUserAdded"/> and <see cref="DiscordClient.GuildScheduledEventUserRemoved"/>.</para>
/// </summary>
GuildScheduledEvents = 1 << 16,
/// <summary>
/// Whether to include automod configuration events.
/// <para>These include <see cref="DiscordClient.AutomodRuleCreated"/>, <see cref="DiscordClient.AutomodRuleUpdated"/> and <see cref="DiscordClient.AutomodRuleDeleted"/>.</para>
/// </summary>
AutoModerationConfiguration = 1 << 20,
/// <summary>
/// Whether to include automod execution events.
/// <para>These includes <see cref="DiscordClient.AutomodActionExecuted"/>.</para>
/// </summary>
AutoModerationExecution = 1 << 21,
/// <summary>
/// Includes all unprivileged intents.
/// <para>These are all intents excluding <see cref="GuildMembers"/> and <see cref="GuildPresences"/>.</para>
/// <para>The <see cref="DiscordIntents.GuildMessages"/> will be excluded as of April 2022.</para>
/// </summary>
AllUnprivileged = Guilds | GuildBans | GuildEmojisAndStickers | GuildIntegrations | GuildWebhooks | GuildInvites | GuildVoiceStates | GuildMessages |
GuildMessageReactions | GuildMessageTyping | DirectMessages | DirectMessageReactions | DirectMessageTyping | GuildScheduledEvents | AutoModerationConfiguration | DiscordIntents.AutoModerationExecution,
/// <summary>
/// Includes all intents.
/// <para>The <see cref="GuildMembers"/>, <see cref="GuildPresences"/> and <see cref="MessageContent"/> intents are privileged, and must be enabled on the bot's developer page.</para>
/// <para>The <see cref="MessageContent"/> exist only in v10.</para>
/// </summary>
All = AllUnprivileged | GuildMembers | GuildPresences | MessageContent,
/// <summary>
/// Includes all intents.
/// <para>The <see cref="GuildMembers"/> and <see cref="GuildPresences"/> intents are privileged, and must be enabled on the bot's developer page.</para>
/// <para>The <see cref="MessageContent"/> exist only in v10 and is removed here.</para>
/// </summary>
AllV9Less = AllUnprivileged | GuildMembers | GuildPresences
}
diff --git a/DisCatSharp/Enums/GatewayCompressionLevel.cs b/DisCatSharp/Enums/GatewayCompressionLevel.cs
index 3afe25eb3..93a6bde4f 100644
--- a/DisCatSharp/Enums/GatewayCompressionLevel.cs
+++ b/DisCatSharp/Enums/GatewayCompressionLevel.cs
@@ -1,44 +1,44 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Determines at which level should the WebSocket traffic be compressed.
/// </summary>
public enum GatewayCompressionLevel : byte
{
/// <summary>
/// Defines that traffic should not be compressed at all.
/// </summary>
None = 0,
/// <summary>
/// Defines that traffic should be compressed at payload level.
/// </summary>
Payload = 1,
/// <summary>
/// Defines that entire traffic stream should be compressed.
/// </summary>
Stream = 2
}
diff --git a/DisCatSharp/Enums/Guild/AuditLogActionCategory.cs b/DisCatSharp/Enums/Guild/AuditLogActionCategory.cs
index eca829619..43e0fba4e 100644
--- a/DisCatSharp/Enums/Guild/AuditLogActionCategory.cs
+++ b/DisCatSharp/Enums/Guild/AuditLogActionCategory.cs
@@ -1,49 +1,49 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Indicates audit log action category.
/// </summary>
public enum AuditLogActionCategory
{
/// <summary>
/// Indicates that this action resulted in creation or addition of an object.
/// </summary>
Create,
/// <summary>
/// Indicates that this action resulted in update of an object.
/// </summary>
Update,
/// <summary>
/// Indicates that this action resulted in deletion or removal of an object.
/// </summary>
Delete,
/// <summary>
/// Indicates that this action resulted in something else than creation, addition, update, deletion, or removal of an object.
/// </summary>
Other
}
diff --git a/DisCatSharp/Enums/Guild/AuditLogActionType.cs b/DisCatSharp/Enums/Guild/AuditLogActionType.cs
index 05dd71d3c..1d44585fa 100644
--- a/DisCatSharp/Enums/Guild/AuditLogActionType.cs
+++ b/DisCatSharp/Enums/Guild/AuditLogActionType.cs
@@ -1,294 +1,294 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents type of the action that was taken in given audit log event.
/// </summary>
public enum AuditLogActionType
{
/// <summary>
/// Indicates an invalid action type.
/// </summary>
Invalid = 0,
/// <summary>
/// Indicates that the guild was updated.
/// </summary>
GuildUpdate = 1,
/// <summary>
/// Indicates that the channel was created.
/// </summary>
ChannelCreate = 10,
/// <summary>
/// Indicates that the channel was updated.
/// </summary>
ChannelUpdate = 11,
/// <summary>
/// Indicates that the channel was deleted.
/// </summary>
ChannelDelete = 12,
/// <summary>
/// Indicates that the channel permission overwrite was created.
/// </summary>
OverwriteCreate = 13,
/// <summary>
/// Indicates that the channel permission overwrite was updated.
/// </summary>
OverwriteUpdate = 14,
/// <summary>
/// Indicates that the channel permission overwrite was deleted.
/// </summary>
OverwriteDelete = 15,
/// <summary>
/// Indicates that the user was kicked.
/// </summary>
Kick = 20,
/// <summary>
/// Indicates that users were pruned.
/// </summary>
Prune = 21,
/// <summary>
/// Indicates that the user was banned.
/// </summary>
Ban = 22,
/// <summary>
/// Indicates that the user was unbanned.
/// </summary>
Unban = 23,
/// <summary>
/// Indicates that the member was updated.
/// </summary>
MemberUpdate = 24,
/// <summary>
/// Indicates that the member's roles were updated.
/// </summary>
MemberRoleUpdate = 25,
/// <summary>
/// Indicates that the member has moved to another voice channel.
/// </summary>
MemberMove = 26,
/// <summary>
/// Indicates that the member has disconnected from a voice channel.
/// </summary>
MemberDisconnect = 27,
/// <summary>
/// Indicates that a bot was added to the guild.
/// </summary>
BotAdd = 28,
/// <summary>
/// Indicates that the role was created.
/// </summary>
RoleCreate = 30,
/// <summary>
/// Indicates that the role was updated.
/// </summary>
RoleUpdate = 31,
/// <summary>
/// Indicates that the role was deleted.
/// </summary>
RoleDelete = 32,
/// <summary>
/// Indicates that the invite was created.
/// </summary>
InviteCreate = 40,
/// <summary>
/// Indicates that the invite was updated.
/// </summary>
InviteUpdate = 41,
/// <summary>
/// Indicates that the invite was deleted.
/// </summary>
InviteDelete = 42,
/// <summary>
/// Indicates that the webhook was created.
/// </summary>
WebhookCreate = 50,
/// <summary>
/// Indicates that the webook was updated.
/// </summary>
WebhookUpdate = 51,
/// <summary>
/// Indicates that the webhook was deleted.
/// </summary>
WebhookDelete = 52,
/// <summary>
/// Indicates that an emoji was created.
/// </summary>
EmojiCreate = 60,
/// <summary>
/// Indicates that an emoji was updated.
/// </summary>
EmojiUpdate = 61,
/// <summary>
/// Indicates that an emoji was deleted.
/// </summary>
EmojiDelete = 62,
/// <summary>
/// Indicates that the message was deleted.
/// </summary>
MessageDelete = 72,
/// <summary>
/// Indicates that messages were bulk-deleted.
/// </summary>
MessageBulkDelete = 73,
/// <summary>
/// Indicates that a message was pinned.
/// </summary>
MessagePin = 74,
/// <summary>
/// Indicates that a message was unpinned.
/// </summary>
MessageUnpin = 75,
/// <summary>
/// Indicates that an integration was created.
/// </summary>
IntegrationCreate = 80,
/// <summary>
/// Indicates that an integration was updated.
/// </summary>
IntegrationUpdate = 81,
/// <summary>
/// Indicates that an integration was deleted.
/// </summary>
IntegrationDelete = 82,
/// <summary>
/// Indicates that an stage instance was created.
/// </summary>
StageInstanceCreate = 83,
/// <summary>
/// Indicates that an stage instance was updated.
/// </summary>
StageInstanceUpdate = 84,
/// <summary>
/// Indicates that an stage instance was deleted.
/// </summary>
StageInstanceDelete = 85,
/// <summary>
/// Indicates that an sticker was created.
/// </summary>
StickerCreate = 90,
/// <summary>
/// Indicates that an sticker was updated.
/// </summary>
StickerUpdate = 91,
/// <summary>
/// Indicates that an sticker was deleted.
/// </summary>
StickerDelete = 92,
/// <summary>
/// Indicates that an event was created.
/// </summary>
GuildScheduledEventCreate = 100,
/// <summary>
/// Indicates that an event was updated.
/// </summary>
GuildScheduledEventUpdate = 101,
/// <summary>
/// Indicates that an event was deleted.
/// </summary>
GuildScheduledEventDelete = 102,
/// <summary>
/// Indicates that an thread was created.
/// </summary>
ThreadCreate = 110,
/// <summary>
/// Indicates that an thread was updated.
/// </summary>
ThreadUpdate = 111,
/// <summary>
/// Indicates that an thread was deleted.
/// </summary>
ThreadDelete = 112,
/// <summary>
/// Indicates that the permissions for an application command was updated.
/// </summary>
ApplicationCommandPermissionUpdate = 121,
/// <summary>
/// Indicates that a new automod rule has been added.
/// </summary>
AutoModerationRuleCreate = 140,
/// <summary>
/// Indicates that a automod rule has been updated.
/// </summary>
AutoModerationRuleUpdate = 141,
/// <summary>
/// Indicates that a automod rule has been deleted.
/// </summary>
AutoModerationRuleDelete = 142,
/// <summary>
/// Indicates that automod blocked a message.
/// </summary>
AutoModerationBlockMessage = 143
}
diff --git a/DisCatSharp/Enums/Guild/Automod/AutomodActionType.cs b/DisCatSharp/Enums/Guild/Automod/AutomodActionType.cs
index 80de4cb2d..0b46ac572 100644
--- a/DisCatSharp/Enums/Guild/Automod/AutomodActionType.cs
+++ b/DisCatSharp/Enums/Guild/Automod/AutomodActionType.cs
@@ -1,43 +1,43 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums
{
public enum AutomodActionType : int
{
/// <summary>
/// Blocks the content of a message according to the rule.
/// </summary>
BlockMessage = 1,
/// <summary>
/// Logs user to a specified channel.
/// </summary>
SendAlertMessage = 2,
/// <summary>
/// Timeout user for a specified duration.
/// Only valid for Keyword and MentionSpam rules
/// </summary>
Timeout = 3
}
}
diff --git a/DisCatSharp/Enums/Guild/Automod/AutomodEventType.cs b/DisCatSharp/Enums/Guild/Automod/AutomodEventType.cs
index 559868101..ac40308a9 100644
--- a/DisCatSharp/Enums/Guild/Automod/AutomodEventType.cs
+++ b/DisCatSharp/Enums/Guild/Automod/AutomodEventType.cs
@@ -1,35 +1,35 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums
{
/// <summary>
/// Represent's a rule's flags.
/// </summary>
public enum AutomodEventType : int
{
/// <summary>
/// Indicates that this rule should be checked when a member sends or edits a message in the guild.
/// </summary>
MessageSend = 1
}
}
diff --git a/DisCatSharp/Enums/Guild/Automod/AutomodKeywordPresetType.cs b/DisCatSharp/Enums/Guild/Automod/AutomodKeywordPresetType.cs
index 8242d4b66..325b47f65 100644
--- a/DisCatSharp/Enums/Guild/Automod/AutomodKeywordPresetType.cs
+++ b/DisCatSharp/Enums/Guild/Automod/AutomodKeywordPresetType.cs
@@ -1,45 +1,45 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums
{
/// <summary>
/// Represents a rule's keyword preset types.
/// </summary>
public enum AutomodKeywordPresetType : int
{
/// <summary>
/// Words that may be considered forms of swearing or cursing.
/// </summary>
Profanity = 1,
/// <summary>
/// Words that refer to sexually explicit behavior or activity.
/// </summary>
SexualContent = 2,
/// <summary>
/// Personal insults or words that may be considered hate speech.
/// </summary>
Slurs = 3
}
}
diff --git a/DisCatSharp/Enums/Guild/Automod/AutomodTriggerType.cs b/DisCatSharp/Enums/Guild/Automod/AutomodTriggerType.cs
index 863e2a6d9..f58714823 100644
--- a/DisCatSharp/Enums/Guild/Automod/AutomodTriggerType.cs
+++ b/DisCatSharp/Enums/Guild/Automod/AutomodTriggerType.cs
@@ -1,59 +1,59 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums
{
/// <summary>
/// Represents a rule's content type.
/// </summary>
public enum AutomodTriggerType : int
{
/// <summary>
/// Checks if content contains words from a user defined list of keywords.
/// Max. 3 per guild.
/// </summary>
Keyword = 1,
/// <summary>
/// Checks if content contains a suspocopis link.
/// </summary>
SuspiciousLinkFilter = 2,
/// <summary>
/// Checks if content represents generic spam.
/// Max. 1 per guild.
/// </summary>
Spam = 3,
/// <summary>
/// Checks if content contains words from internal pre-defined wordsets.
/// Max. 1 per guild.
/// </summary>
KeywordPreset = 4,
/// <summary>
/// Checks if content contains more unique mentions than allowed.
/// Max. 1 per guild.
/// </summary>
MentionSpam = 5
}
}
diff --git a/DisCatSharp/Enums/Guild/DefaultMessageNotifications.cs b/DisCatSharp/Enums/Guild/DefaultMessageNotifications.cs
index dd9ec4f43..8d2bdcaed 100644
--- a/DisCatSharp/Enums/Guild/DefaultMessageNotifications.cs
+++ b/DisCatSharp/Enums/Guild/DefaultMessageNotifications.cs
@@ -1,39 +1,39 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents default notification level for a guild.
/// </summary>
public enum DefaultMessageNotifications : int
{
/// <summary>
/// All messages will trigger push notifications.
/// </summary>
AllMessages = 0,
/// <summary>
/// Only messages that mention the user (or a role he's in) will trigger push notifications.
/// </summary>
MentionsOnly = 1
}
diff --git a/DisCatSharp/Enums/Guild/ExplicitContentFilter.cs b/DisCatSharp/Enums/Guild/ExplicitContentFilter.cs
index d4ce64b8b..4aff0dff6 100644
--- a/DisCatSharp/Enums/Guild/ExplicitContentFilter.cs
+++ b/DisCatSharp/Enums/Guild/ExplicitContentFilter.cs
@@ -1,44 +1,44 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents the value of explicit content filter in a guild.
/// </summary>
public enum ExplicitContentFilter : int
{
/// <summary>
/// Explicit content filter is disabled.
/// </summary>
Disabled = 0,
/// <summary>
/// Only messages from members without any roles are scanned.
/// </summary>
MembersWithoutRoles = 1,
/// <summary>
/// Messages from all members are scanned.
/// </summary>
AllMembers = 2
}
diff --git a/DisCatSharp/Enums/Guild/HubType.cs b/DisCatSharp/Enums/Guild/HubType.cs
index 2b7d4a518..85dc3eb70 100644
--- a/DisCatSharp/Enums/Guild/HubType.cs
+++ b/DisCatSharp/Enums/Guild/HubType.cs
@@ -1,44 +1,44 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents a guilds hub type.
/// </summary>
public enum HubType : int
{
/// <summary>
/// Indicates that the hub is a default one.
/// </summary>
Default = 0,
/// <summary>
/// Indicates that the hub is a high school.
/// </summary>
HighSchool = 1,
/// <summary>
/// Indicates that the hub is a college.
/// </summary>
College = 2
}
diff --git a/DisCatSharp/Enums/Guild/MemberFlags.cs b/DisCatSharp/Enums/Guild/MemberFlags.cs
index 0a9759136..a9dcf875f 100644
--- a/DisCatSharp/Enums/Guild/MemberFlags.cs
+++ b/DisCatSharp/Enums/Guild/MemberFlags.cs
@@ -1,65 +1,65 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Attributes;
namespace DisCatSharp.Enums;
/// <summary>
/// Represents additional details of a member account.
/// </summary>
[Flags]
public enum MemberFlags : long
{
/// <summary>
/// Member has no flags.
/// </summary>
None = 0,
/// <summary>
/// Member has left and rejoined the guild.
/// </summary>
DidRejoin = 1 << 0,
/// <summary>
/// Member has completed onboarding.
/// </summary>
[DiscordInExperiment]
CompletedOnboarding = 1 << 1,
/// <summary>
/// Member bypasses guild verification requirements.
/// </summary>
[DiscordInExperiment]
BypassesVerification = 1 << 2,
[DiscordInExperiment]
Verified = BypassesVerification,
/// <summary>
/// Member has started onboarding.
/// </summary>
[DiscordInExperiment]
STARTED_ONBOARDING = 1 << 3,
}
diff --git a/DisCatSharp/Enums/Guild/MembershipScreeningFieldType.cs b/DisCatSharp/Enums/Guild/MembershipScreeningFieldType.cs
index 1acac988a..86d814968 100644
--- a/DisCatSharp/Enums/Guild/MembershipScreeningFieldType.cs
+++ b/DisCatSharp/Enums/Guild/MembershipScreeningFieldType.cs
@@ -1,41 +1,41 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Runtime.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace DisCatSharp.Enums;
/// <summary>
/// Represents a membership screening field type
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum MembershipScreeningFieldType
{
/// <summary>
/// Specifies the server rules
/// </summary>
[EnumMember(Value = "TERMS")]
Terms
}
diff --git a/DisCatSharp/Enums/Guild/MfaLevel.cs b/DisCatSharp/Enums/Guild/MfaLevel.cs
index a1bbc1d83..dd9a5ffeb 100644
--- a/DisCatSharp/Enums/Guild/MfaLevel.cs
+++ b/DisCatSharp/Enums/Guild/MfaLevel.cs
@@ -1,39 +1,39 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents multi-factor authentication level required by a guild to use administrator functionality.
/// </summary>
public enum MfaLevel : int
{
/// <summary>
/// Multi-factor authentication is not required to use administrator functionality.
/// </summary>
Disabled = 0,
/// <summary>
/// Multi-factor authentication is required to use administrator functionality.
/// </summary>
Enabled = 1
}
diff --git a/DisCatSharp/Enums/Guild/NsfwLevel.cs b/DisCatSharp/Enums/Guild/NsfwLevel.cs
index af9072dae..8771bf27c 100644
--- a/DisCatSharp/Enums/Guild/NsfwLevel.cs
+++ b/DisCatSharp/Enums/Guild/NsfwLevel.cs
@@ -1,51 +1,51 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a guild's content level.
/// </summary>
public enum NsfwLevel
{
/// <summary>
/// Indicates the guild has no special NSFW level.
/// </summary>
Default = 0,
/// <summary>
/// Indicates the guild has extremely suggestive or mature content that would only be suitable for users over 18
/// </summary>
Explicit = 1,
/// <summary>
/// Indicates the guild has no content that could be deemed NSFW. It is SFW.
/// </summary>
Safe = 2,
/// <summary>
/// Indicates the guild has mildly NSFW content that may not be suitable for users under 18.
/// </summary>
Age_Restricted = 3
}
diff --git a/DisCatSharp/Enums/Guild/PremiumTier.cs b/DisCatSharp/Enums/Guild/PremiumTier.cs
index 298119f9c..0d4031c05 100644
--- a/DisCatSharp/Enums/Guild/PremiumTier.cs
+++ b/DisCatSharp/Enums/Guild/PremiumTier.cs
@@ -1,54 +1,54 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents a server's premium tier.
/// </summary>
public enum PremiumTier : int
{
/// <summary>
/// Indicates that this server was not boosted.
/// </summary>
None = 0,
/// <summary>
/// Indicates that this server was boosted two times.
/// </summary>
TierOne = 1,
/// <summary>
/// Indicates that this server was boosted seven times.
/// </summary>
TierTwo = 2,
/// <summary>
/// Indicates that this server was boosted fourteen times.
/// </summary>
TierThree = 3,
/// <summary>
/// Indicates an unknown premium tier.
/// </summary>
Unknown = int.MaxValue
}
diff --git a/DisCatSharp/Enums/Guild/PriceTierType.cs b/DisCatSharp/Enums/Guild/PriceTierType.cs
index 1ce3c4874..01f8651d7 100644
--- a/DisCatSharp/Enums/Guild/PriceTierType.cs
+++ b/DisCatSharp/Enums/Guild/PriceTierType.cs
@@ -1,34 +1,34 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents a price tier type.
/// </summary>
public enum PriceTierType : int
{
/// <summary>
/// Indicates that this is a role subscription.
/// </summary>
GuildRoleSubscriptions = 1
}
diff --git a/DisCatSharp/Enums/Guild/RoleFlags.cs b/DisCatSharp/Enums/Guild/RoleFlags.cs
index 1d2e0bc15..6d4475e62 100644
--- a/DisCatSharp/Enums/Guild/RoleFlags.cs
+++ b/DisCatSharp/Enums/Guild/RoleFlags.cs
@@ -1,43 +1,43 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents additional details of a role.
/// </summary>
[Flags]
public enum RoleFlags
{
/// <summary>
/// This role has no flags.
/// </summary>
None = 0,
/// <summary>
/// This role is in a prompt.
/// </summary>
InPrompt = 1 << 0,
}
diff --git a/DisCatSharp/Enums/Guild/ScheduledEvent/ScheduledEventEntityType.cs b/DisCatSharp/Enums/Guild/ScheduledEvent/ScheduledEventEntityType.cs
index 5e89517c9..a50599d24 100644
--- a/DisCatSharp/Enums/Guild/ScheduledEvent/ScheduledEventEntityType.cs
+++ b/DisCatSharp/Enums/Guild/ScheduledEvent/ScheduledEventEntityType.cs
@@ -1,44 +1,44 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents the entity type for a scheduled event.
/// </summary>
public enum ScheduledEventEntityType : int
{
/// <summary>
/// Indicates that the events is hold in a stage instance.
/// </summary>
StageInstance = 1,
/// <summary>
/// Indicates that the events is hold in a voice channel.
/// </summary>
Voice = 2,
/// <summary>
/// Indicates that the events is hold external.
/// </summary>
External = 3
}
diff --git a/DisCatSharp/Enums/Guild/ScheduledEvent/ScheduledEventPrivacyLevel.cs b/DisCatSharp/Enums/Guild/ScheduledEvent/ScheduledEventPrivacyLevel.cs
index 2494912dc..00005be2d 100644
--- a/DisCatSharp/Enums/Guild/ScheduledEvent/ScheduledEventPrivacyLevel.cs
+++ b/DisCatSharp/Enums/Guild/ScheduledEvent/ScheduledEventPrivacyLevel.cs
@@ -1,39 +1,39 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents the privacy level for a guild scheduled event.
/// </summary>
public enum ScheduledEventPrivacyLevel : int
{
/// <summary>
/// Indicates that the guild scheduled event is public.
/// </summary>
Public = 1,
/// <summary>
/// Indicates that the the guild scheduled event is only accessible to guild members.
/// </summary>
GuildOnly = 2
}
diff --git a/DisCatSharp/Enums/Guild/ScheduledEvent/ScheduledEventStatus.cs b/DisCatSharp/Enums/Guild/ScheduledEvent/ScheduledEventStatus.cs
index 09e958564..21f70a4ed 100644
--- a/DisCatSharp/Enums/Guild/ScheduledEvent/ScheduledEventStatus.cs
+++ b/DisCatSharp/Enums/Guild/ScheduledEvent/ScheduledEventStatus.cs
@@ -1,49 +1,49 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents the status for a scheduled event.
/// </summary>
public enum ScheduledEventStatus : int
{
/// <summary>
/// Indicates that the event is scheduled.
/// </summary>
Scheduled = 1,
/// <summary>
/// Indicates that the event is active.
/// </summary>
Active = 2,
/// <summary>
/// Indicates that the event is completed.
/// </summary>
Completed = 3,
/// <summary>
/// Indicates that the event is canceled.
/// </summary>
Canceled = 4
}
diff --git a/DisCatSharp/Enums/Guild/Stage/StagePrivacyLevel.cs b/DisCatSharp/Enums/Guild/Stage/StagePrivacyLevel.cs
index f8759d01c..2d59dd0e4 100644
--- a/DisCatSharp/Enums/Guild/Stage/StagePrivacyLevel.cs
+++ b/DisCatSharp/Enums/Guild/Stage/StagePrivacyLevel.cs
@@ -1,44 +1,44 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Attributes;
namespace DisCatSharp.Enums;
/// <summary>
/// Represents the privacy level for a stage.
/// </summary>
[DiscordDeprecated("Discord removed the feature for stage discovery. Option is defaulting to GuildOnly."), Obsolete]
public enum StagePrivacyLevel : int
{
/// <summary>
/// Indicates that the stage is public visible.
/// </summary>
Public = 1,
/// <summary>
/// Indicates that the stage is only visible to guild members.
/// </summary>
GuildOnly = 2
}
diff --git a/DisCatSharp/Enums/Guild/SystemChannelFlags.cs b/DisCatSharp/Enums/Guild/SystemChannelFlags.cs
index da1ef7233..7b57b79e3 100644
--- a/DisCatSharp/Enums/Guild/SystemChannelFlags.cs
+++ b/DisCatSharp/Enums/Guild/SystemChannelFlags.cs
@@ -1,76 +1,76 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents a system channel flags extension.
/// </summary>
public static class SystemChannelFlagsExtension
{
/// <summary>
/// Calculates whether these system channel flags contain a specific flag.
/// </summary>
/// <param name="baseFlags">The existing flags.</param>
/// <param name="flag">The flag to search for.</param>
/// <returns></returns>
public static bool HasSystemChannelFlag(this SystemChannelFlags baseFlags, SystemChannelFlags flag) => (baseFlags & flag) == flag;
}
/// <summary>
/// Represents settings for a guild's system channel.
/// </summary>
[Flags]
public enum SystemChannelFlags
{
/// <summary>
/// Member join messages are disabled.
/// </summary>
SuppressJoinNotifications = 1 << 0,
/// <summary>
/// Server boost messages are disabled.
/// </summary>
SuppressPremiumSubscriptions = 1 << 1,
/// <summary>
/// Server setup tips are disabled.
/// </summary>
SuppressGuildReminderNotifications = 1 << 2,
/// <summary>
/// Suppress member join sticker replies.
/// </summary>
SuppressJoinNotificationReplies = 1 << 3,
/// <summary>
/// Role subscription purchase messages are disabled.
/// </summary>
SuppressRoleSubbscriptionPurchaseNotification = 1<<4,
/// <summary>
/// Suppress role subscription purchase sticker replies.
/// </summary>
SuppressRoleSubbscriptionPurchaseNotificationReplies = 1<<5,
}
diff --git a/DisCatSharp/Enums/Guild/ThreadAndForum/ForumLayout.cs b/DisCatSharp/Enums/Guild/ThreadAndForum/ForumLayout.cs
index 59adb3e21..bbe0cad2b 100644
--- a/DisCatSharp/Enums/Guild/ThreadAndForum/ForumLayout.cs
+++ b/DisCatSharp/Enums/Guild/ThreadAndForum/ForumLayout.cs
@@ -1,44 +1,44 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents the layout for posts in a forum channel.
/// </summary>
public enum ForumLayout : int
{
/// <summary>
/// Forum channel will use default layout settings.
/// </summary>
Default = 0,
/// <summary>
/// Forum channel will display posts in a list layout.
/// </summary>
List = 1,
/// <summary>
/// Forum channel will display posts in a grid layout.
/// </summary>
Grid = 2
}
diff --git a/DisCatSharp/Enums/Guild/ThreadAndForum/ForumPostSortOrder.cs b/DisCatSharp/Enums/Guild/ThreadAndForum/ForumPostSortOrder.cs
index 3cdfa6737..1f64b482e 100644
--- a/DisCatSharp/Enums/Guild/ThreadAndForum/ForumPostSortOrder.cs
+++ b/DisCatSharp/Enums/Guild/ThreadAndForum/ForumPostSortOrder.cs
@@ -1,39 +1,39 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents the default sort order for posts in a forum channel.
/// </summary>
public enum ForumPostSortOrder : int
{
/// <summary>
/// Sort forum posts by activity.
/// </summary>
LatestActivity = 0,
/// <summary>
/// Sort forum posts by creation time (from most recent to oldest).
/// </summary>
CreationDate = 1
}
diff --git a/DisCatSharp/Enums/Guild/ThreadAndForum/ThreadAutoArchiveDuration.cs b/DisCatSharp/Enums/Guild/ThreadAndForum/ThreadAutoArchiveDuration.cs
index d9d0bbe82..210fc4a32 100644
--- a/DisCatSharp/Enums/Guild/ThreadAndForum/ThreadAutoArchiveDuration.cs
+++ b/DisCatSharp/Enums/Guild/ThreadAndForum/ThreadAutoArchiveDuration.cs
@@ -1,49 +1,49 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents the auto-archive duration for a thread.
/// </summary>
public enum ThreadAutoArchiveDuration : int
{
/// <summary>
/// Indicates that the thread will be auto archived after one hour.
/// </summary>
OneHour = 60,
/// <summary>
/// Indicates that the thread will be auto archived after one day / 24 hours.
/// </summary>
OneDay = 1440,
/// <summary>
/// Indicates that the thread will be auto archived after three days.
/// </summary>
ThreeDays = 4320,
/// <summary>
/// Indicates that the thread will be auto archived after a week.
/// </summary>
OneWeek = 10080
}
diff --git a/DisCatSharp/Enums/Guild/ThreadAndForum/ThreadMemberFlags.cs b/DisCatSharp/Enums/Guild/ThreadAndForum/ThreadMemberFlags.cs
index 1ece780f1..0dd1f8798 100644
--- a/DisCatSharp/Enums/Guild/ThreadAndForum/ThreadMemberFlags.cs
+++ b/DisCatSharp/Enums/Guild/ThreadAndForum/ThreadMemberFlags.cs
@@ -1,49 +1,49 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents notification settings for a thread.
/// </summary>
public enum ThreadMemberFlags : int
{
/// <summary>
/// Indicates that the notification setting is set to has interacted.
/// </summary>
HasInteracted = 1,
/// <summary>
/// Indicates that the notification setting is set to all messages.
/// </summary>
AllMessages = 2,
/// <summary>
/// Indicates that the notification setting is set to only mentions.
/// </summary>
OnlyMentions = 4,
/// <summary>
/// Indicates that the notification setting is set to none.
/// </summary>
None = 8
}
diff --git a/DisCatSharp/Enums/Guild/VerificationLevel.cs b/DisCatSharp/Enums/Guild/VerificationLevel.cs
index 4462d2a54..47f771383 100644
--- a/DisCatSharp/Enums/Guild/VerificationLevel.cs
+++ b/DisCatSharp/Enums/Guild/VerificationLevel.cs
@@ -1,54 +1,54 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents guild verification level.
/// </summary>
public enum VerificationLevel : int
{
/// <summary>
/// No verification. Anyone can join and chat right away.
/// </summary>
None = 0,
/// <summary>
/// Low verification level. Users are required to have a verified email attached to their account in order to be able to chat.
/// </summary>
Low = 1,
/// <summary>
/// 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.
/// </summary>
Medium = 2,
/// <summary>
/// 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.
/// </summary>
High = 3,
/// <summary>
/// Highest verification level. Users are required to have a verified phone number attached to their account.
/// </summary>
Highest = 4
}
diff --git a/DisCatSharp/Enums/Guild/WidgetType.cs b/DisCatSharp/Enums/Guild/WidgetType.cs
index a56d4d9ec..c27e7a83f 100644
--- a/DisCatSharp/Enums/Guild/WidgetType.cs
+++ b/DisCatSharp/Enums/Guild/WidgetType.cs
@@ -1,55 +1,55 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents the formats for a guild widget.
/// </summary>
public enum WidgetType : int
{
/// <summary>
/// The widget is represented in shield format.
/// <para>This is the default widget type.</para>
/// </summary>
Shield = 0,
/// <summary>
/// The widget is represented as the first banner type.
/// </summary>
Banner1 = 1,
/// <summary>
/// The widget is represented as the second banner type.
/// </summary>
Banner2 = 2,
/// <summary>
/// The widget is represented as the third banner type.
/// </summary>
Banner3 = 3,
/// <summary>
/// The widget is represented in the fourth banner type.
/// </summary>
Banner4 = 4
}
diff --git a/DisCatSharp/Enums/Integration/IntegrationExpireBehavior.cs b/DisCatSharp/Enums/Integration/IntegrationExpireBehavior.cs
index f781740c2..3f3ecd9cb 100644
--- a/DisCatSharp/Enums/Integration/IntegrationExpireBehavior.cs
+++ b/DisCatSharp/Enums/Integration/IntegrationExpireBehavior.cs
@@ -1,39 +1,39 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents the integration expire behavior.
/// </summary>
public enum IntegrationExpireBehavior : int
{
/// <summary>
/// Removes the role from the member.
/// </summary>
RemoveRole = 0,
/// <summary>
/// Kicks the member.
/// </summary>
Kick = 1
}
diff --git a/DisCatSharp/Enums/Interaction/ButtonStyle.cs b/DisCatSharp/Enums/Interaction/ButtonStyle.cs
index 4fb9b4be4..37edd2873 100644
--- a/DisCatSharp/Enums/Interaction/ButtonStyle.cs
+++ b/DisCatSharp/Enums/Interaction/ButtonStyle.cs
@@ -1,54 +1,54 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents a button's style/color.
/// </summary>
public enum ButtonStyle : int
{
/// <summary>
/// Blurple button.
/// </summary>
Primary = 1,
/// <summary>
/// Grey button.
/// </summary>
Secondary = 2,
/// <summary>
/// Green button.
/// </summary>
Success = 3,
/// <summary>
/// Red button.
/// </summary>
Danger = 4,
/// <summary>
/// Link Button.
/// </summary>
Link = 5
}
diff --git a/DisCatSharp/Enums/Interaction/ComponentType.cs b/DisCatSharp/Enums/Interaction/ComponentType.cs
index 8c5cac979..a34a7a447 100644
--- a/DisCatSharp/Enums/Interaction/ComponentType.cs
+++ b/DisCatSharp/Enums/Interaction/ComponentType.cs
@@ -1,69 +1,69 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents a type of component.
/// </summary>
public enum ComponentType
{
/// <summary>
/// A row of components.
/// </summary>
ActionRow = 1,
/// <summary>
/// A button.
/// </summary>
Button = 2,
/// <summary>
/// A select menu to select strings.
/// </summary>
StringSelect = 3,
/// <summary>
/// A input text.
/// </summary>
InputText = 4,
/// <summary>
/// A select menu to select users.
/// </summary>
UserSelect = 5,
/// <summary>
/// A select menu to select roles.
/// </summary>
RoleSelect = 6,
/// <summary>
/// A select menu to select menu to select users and roles.
/// </summary>
MentionableSelect = 7,
/// <summary>
/// A select menu to select channels.
/// </summary>
ChannelSelect = 8,
}
diff --git a/DisCatSharp/Enums/Interaction/InteractionResponseType.cs b/DisCatSharp/Enums/Interaction/InteractionResponseType.cs
index 808c5ce82..461bbbddf 100644
--- a/DisCatSharp/Enums/Interaction/InteractionResponseType.cs
+++ b/DisCatSharp/Enums/Interaction/InteractionResponseType.cs
@@ -1,64 +1,64 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents the type of interaction response
/// </summary>
public enum InteractionResponseType
{
/// <summary>
/// Acknowledges a Ping.
/// </summary>
Pong = 1,
/// <summary>
/// Responds to the interaction with a message.
/// </summary>
ChannelMessageWithSource = 4,
/// <summary>
/// Acknowledges an interaction to edit to a response later. The user sees a "thinking" state.
/// </summary>
DeferredChannelMessageWithSource = 5,
/// <summary>
/// Acknowledges a component interaction to allow a response later.
/// </summary>
DeferredMessageUpdate = 6,
/// <summary>
/// Responds to a component interaction by editing the message it's attached to.
/// </summary>
UpdateMessage = 7,
/// <summary>
/// Responds to an auto-complete request.
/// </summary>
AutoCompleteResult = 8,
/// <summary>
/// Responds to the interaction with a modal.
/// </summary>
Modal = 9
}
diff --git a/DisCatSharp/Enums/Interaction/InteractionType.cs b/DisCatSharp/Enums/Interaction/InteractionType.cs
index 3f7252eac..758328786 100644
--- a/DisCatSharp/Enums/Interaction/InteractionType.cs
+++ b/DisCatSharp/Enums/Interaction/InteractionType.cs
@@ -1,54 +1,54 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents the type of interaction used.
/// </summary>
public enum InteractionType
{
/// <summary>
/// Sent when registering an HTTP interaction endpoint with Discord. Must be replied to with a Pong.
/// </summary>
Ping = 1,
/// <summary>
/// An application command.
/// </summary>
ApplicationCommand = 2,
/// <summary>
/// A component.
/// </summary>
Component = 3,
/// <summary>
/// An autocomplete field.
/// </summary>
AutoComplete = 4,
/// <summary>
/// A modal component.
/// </summary>
ModalSubmit = 5
}
diff --git a/DisCatSharp/Enums/Interaction/TextComponentStyle.cs b/DisCatSharp/Enums/Interaction/TextComponentStyle.cs
index b86e2f3cb..029adb309 100644
--- a/DisCatSharp/Enums/Interaction/TextComponentStyle.cs
+++ b/DisCatSharp/Enums/Interaction/TextComponentStyle.cs
@@ -1,39 +1,39 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents a button's style/color.
/// </summary>
public enum TextComponentStyle : int
{
/// <summary>
/// A small text input.
/// </summary>
Small = 1,
/// <summary>
/// A paragraph text input.
/// </summary>
Paragraph = 2
}
diff --git a/DisCatSharp/Enums/Invite/InviteType.cs b/DisCatSharp/Enums/Invite/InviteType.cs
index 8e15ea534..2787cea1a 100644
--- a/DisCatSharp/Enums/Invite/InviteType.cs
+++ b/DisCatSharp/Enums/Invite/InviteType.cs
@@ -1,44 +1,44 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents the invite type .
/// </summary>
public enum InviteType
{
/// <summary>
/// Represents a guild invite.
/// </summary>
Guild = 0,
/// <summary>
/// Represents a group dm invite.
/// </summary>
GroupDm = 1,
/// <summary>
/// Represents a friend invite.
/// </summary>
User = 2
}
diff --git a/DisCatSharp/Enums/Invite/TargetType.cs b/DisCatSharp/Enums/Invite/TargetType.cs
index 8f1004f0c..572d23aec 100644
--- a/DisCatSharp/Enums/Invite/TargetType.cs
+++ b/DisCatSharp/Enums/Invite/TargetType.cs
@@ -1,51 +1,51 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents the invite type .
/// </summary>
public enum TargetType
{
/// <summary>
/// Represents a streaming invite.
/// </summary>
Streaming = 1,
/// <summary>
/// Represents a activity invite.
/// </summary>
EmbeddedApplication = 2,
/// <summary>
/// Represents a role subscription invite.
/// Not creatable by bots.
/// </summary>
RoleSubscriptionsPurchase = 3,
/// <summary>
/// Represents a promo page generated invite.
/// Not creatable, system generated.
/// </summary>
PromoPage = 4
}
diff --git a/DisCatSharp/Enums/Message/MentionType.cs b/DisCatSharp/Enums/Message/MentionType.cs
index b8c3cf66a..cb5c5687a 100644
--- a/DisCatSharp/Enums/Message/MentionType.cs
+++ b/DisCatSharp/Enums/Message/MentionType.cs
@@ -1,54 +1,54 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Type of mention being made
/// </summary>
public enum MentionType
{
/// <summary>
/// No mention (wtf?)
/// </summary>
None = 0,
/// <summary>
/// Mentioned Username
/// </summary>
Username = 1,
/// <summary>
/// Mentioned Nickname
/// </summary>
Nickname = 2,
/// <summary>
/// Mentioned Channel
/// </summary>
Channel = 4,
/// <summary>
/// Mentioned Role
/// </summary>
Role = 8
}
diff --git a/DisCatSharp/Enums/Message/MessageActivityType.cs b/DisCatSharp/Enums/Message/MessageActivityType.cs
index 3eb02eab8..ac4bd61d5 100644
--- a/DisCatSharp/Enums/Message/MessageActivityType.cs
+++ b/DisCatSharp/Enums/Message/MessageActivityType.cs
@@ -1,49 +1,49 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Indicates the type of MessageActivity for the Rich Presence.
/// </summary>
public enum MessageActivityType
{
/// <summary>
/// Invites the user to join.
/// </summary>
Join = 1,
/// <summary>
/// Invites the user to spectate.
/// </summary>
Spectate = 2,
/// <summary>
/// Invites the user to listen.
/// </summary>
Listen = 3,
/// <summary>
/// Allows the user to request to join.
/// </summary>
JoinRequest = 4
}
diff --git a/DisCatSharp/Enums/Message/MessageFlags.cs b/DisCatSharp/Enums/Message/MessageFlags.cs
index aa5120626..475e1a92e 100644
--- a/DisCatSharp/Enums/Message/MessageFlags.cs
+++ b/DisCatSharp/Enums/Message/MessageFlags.cs
@@ -1,102 +1,102 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents a message flag extensions.
/// </summary>
public static class MessageFlagExtensions
{
/// <summary>
/// Calculates whether these message flags contain a specific flag.
/// </summary>
/// <param name="baseFlags">The existing flags.</param>
/// <param name="flag">The flags to search for.</param>
/// <returns></returns>
public static bool HasMessageFlag(this MessageFlags baseFlags, MessageFlags flag) => (baseFlags & flag) == flag;
}
/// <summary>
/// Represents additional features of a message.
/// </summary>
[Flags]
public enum MessageFlags
{
/// <summary>
/// Whether this message is the original message that was published from a news channel to subscriber channels.
/// </summary>
Crossposted = 1 << 0,
/// <summary>
/// Whether this message is crossposted (automatically posted in a subscriber channel).
/// </summary>
IsCrosspost = 1 << 1,
/// <summary>
/// Whether any embeds in the message are hidden.
/// </summary>
SuppressedEmbeds = 1 << 2,
/// <summary>
/// The source message for this crosspost has been deleted.
/// </summary>
SourceMessageDelete = 1 << 3,
/// <summary>
/// The message came from the urgent message system.
/// </summary>
Urgent = 1 << 4,
/// <summary>
/// The message has an associated thread, with the same id as the message.
/// </summary>
HasThread = 1 << 5,
/// <summary>
/// The message is only visible to the user who invoked the interaction.
/// </summary>
Ephemeral = 1 << 6,
/// <summary>
/// The message is an interaction response and the bot is "thinking".
/// </summary>
Loading = 1 << 7,
/// <summary>
/// The message is warning that some roles failed to mention in thread.
/// </summary>
FailedToMentionSomeRolesInThread = 1 << 8,
/// <summary>
/// The message contains a link marked as potential dangerous or absusive.
/// </summary>
ShouldShowLinkNotDiscordWarning = 1 << 10,
/// <summary>
/// The message suppresses channel notifications.
/// Aka. new message indicator.
/// </summary>
SuppressNotifications = 1 << 12
}
diff --git a/DisCatSharp/Enums/Message/MessageType.cs b/DisCatSharp/Enums/Message/MessageType.cs
index 974c3bbdd..18bdcc8fe 100644
--- a/DisCatSharp/Enums/Message/MessageType.cs
+++ b/DisCatSharp/Enums/Message/MessageType.cs
@@ -1,194 +1,194 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents the type of a message.
/// </summary>
public enum MessageType : int
{
/// <summary>
/// Indicates a regular message.
/// </summary>
Default = 0,
/// <summary>
/// Message indicating a recipient was added to a group direct message or a thread channel.
/// </summary>
RecipientAdd = 1,
/// <summary>
/// Message indicating a recipient was removed from a group direct message or a thread channel.
/// </summary>
RecipientRemove = 2,
/// <summary>
/// Message indicating a call.
/// </summary>
Call = 3,
/// <summary>
/// Message indicating a group direct message or thread channel rename.
/// </summary>
ChannelNameChange = 4,
/// <summary>
/// Message indicating a group direct message channel icon change.
/// </summary>
ChannelIconChange = 5,
/// <summary>
/// Message indicating a user pinned a message to a channel.
/// </summary>
ChannelPinnedMessage = 6,
/// <summary>
/// Message indicating a guild member joined. Most frequently seen in newer, smaller guilds.
/// </summary>
GuildMemberJoin = 7,
/// <summary>
/// Message indicating a member nitro boosted a guild.
/// </summary>
UserPremiumGuildSubscription = 8,
/// <summary>
/// Message indicating a guild reached tier one of nitro boosts.
/// </summary>
TierOneUserPremiumGuildSubscription = 9,
/// <summary>
/// Message indicating a guild reached tier two of nitro boosts.
/// </summary>
TierTwoUserPremiumGuildSubscription = 10,
/// <summary>
/// Message indicating a guild reached tier three of nitro boosts.
/// </summary>
TierThreeUserPremiumGuildSubscription = 11,
/// <summary>
/// Message indicating a user followed a news channel.
/// </summary>
ChannelFollowAdd = 12,
/// <summary>
/// Message indicating a user is streaming in a guild.
/// </summary>
GuildStream = 13,
/// <summary>
/// Message indicating a guild was removed from guild discovery.
/// </summary>
GuildDiscoveryDisqualified = 14,
/// <summary>
/// Message indicating a guild was re-added to guild discovery.
/// </summary>
GuildDiscoveryRequalified = 15,
/// <summary>
/// Message indicating that a guild has failed to meet guild discovery requirements for a week.
/// </summary>
GuildDiscoveryGracePeriodInitialWarning = 16,
/// <summary>
/// Message indicating that a guild has failed to meet guild discovery requirements for 3 weeks.
/// </summary>
GuildDiscoveryGracePeriodFinalWarning = 17,
/// <summary>
/// Message indicating a thread was created.
/// </summary>
ThreadCreated = 18,
/// <summary>
/// Message indicating a user replied to another user.
/// </summary>
Reply = 19,
/// <summary>
/// Message indicating an slash command was invoked.
/// </summary>
ChatInputCommand = 20,
/// <summary>
/// Message indicating a new was message sent as the first message in threads that are started from an existing message in the parent channel.
/// </summary>
ThreadStarterMessage = 21,
/// <summary>
/// Message reminding you to invite people to help you build the server.
/// </summary>
GuildInviteReminder = 22,
/// <summary>
/// Message indicating an context menu command was invoked.
/// </summary>
ContextMenuCommand = 23,
/// <summary>
/// Message indicating the guilds automod acted.
/// </summary>
AutoModerationAction = 24,
/// <summary>
/// Message indicating that a member purchased a role subscription.
/// </summary>
RoleSubscriptionPurchase = 25,
/// <summary>
/// Unknown.
/// </summary>
InteractionPremiumUpsell = 26,
/// <summary>
/// Message indicating that a stage started.
/// </summary>
StageStart = 27,
/// <summary>
/// Message indicating that a stage ended.
/// </summary>
StageEnd = 28,
/// <summary>
/// Message indicating that a user is now a stage speaker.
/// </summary>
StageSpeaker = 29,
/// <summary>
/// Message indicating that a user in a stage raised there hand (Request to speak).
/// </summary>
StageRaiseHand = 30,
/// <summary>
/// Message indicating that a stage topic was changed.
/// </summary>
StageTopic = 31,
/// <summary>
/// Message indicating that a member purchased a application premium subscription.
/// </summary>
GuildApplicationPremiumSubscription = 32
}
diff --git a/DisCatSharp/Enums/Message/TimestampFormat.cs b/DisCatSharp/Enums/Message/TimestampFormat.cs
index 723ca6019..fb053aee0 100644
--- a/DisCatSharp/Enums/Message/TimestampFormat.cs
+++ b/DisCatSharp/Enums/Message/TimestampFormat.cs
@@ -1,64 +1,64 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Denotes the type of formatting to use for timestamps.
/// </summary>
public enum TimestampFormat : byte
{
/// <summary>
/// A short date. e.g. 18/06/2021.
/// </summary>
ShortDate = (byte)'d',
/// <summary>
/// A long date. e.g. 18 June 2021.
/// </summary>
LongDate = (byte)'D',
/// <summary>
/// A short date and time. e.g. 18 June 2021 03:50.
/// </summary>
ShortDateTime = (byte)'f',
/// <summary>
/// A long date and time. e.g. Friday 18 June 2021 03:50.
/// </summary>
LongDateTime = (byte)'F',
/// <summary>
/// A short time. e.g. 03:50.
/// </summary>
ShortTime = (byte)'t',
/// <summary>
/// A long time. e.g. 03:50:15.
/// </summary>
LongTime = (byte)'T',
/// <summary>
/// The time relative to the client. e.g. An hour ago.
/// </summary>
RelativeTime = (byte)'R'
}
diff --git a/DisCatSharp/Enums/OAuth.cs b/DisCatSharp/Enums/OAuth.cs
index 234276da7..0345c20e2 100644
--- a/DisCatSharp/Enums/OAuth.cs
+++ b/DisCatSharp/Enums/OAuth.cs
@@ -1,122 +1,122 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// The oauth scopes.
/// </summary>
public static class OAuth
{
/// <summary>
/// The default scopes for bots.
/// </summary>
private const string BOT_DEFAULT = "bot applications.commands"; // applications.commands.permissions.update
/// <summary>
/// The bot minimal scopes.
/// </summary>
private const string BOT_MINIMAL = "bot applications.commands";
/// <summary>
/// The bot only scope.
/// </summary>
private const string BOT_ONLY = "bot";
/// <summary>
/// The basic identify scopes.
/// </summary>
private const string IDENTIFY_BASIC = "identify email";
/// <summary>
/// The extended identify scopes.
/// </summary>
private const string IDENTIFY_EXTENDED = "identify email guilds guilds.members.read connections";
/// <summary>
/// The role connection scope.
/// </summary>
private const string ROLE_CONNECTIONS_WRITE = "role_connections.write";
/// <summary>
/// All scopes for bots and identify.
/// </summary>
private const string ALL = BOT_DEFAULT + " " + IDENTIFY_EXTENDED + " " + ROLE_CONNECTIONS_WRITE;
/// <summary>
/// Resolves the scopes.
/// </summary>
/// <param name="scope">The scope.</param>
/// <returns>A string representing the scopes.</returns>
public static string ResolveScopes(OAuthScopes scope) =>
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,
};
}
/// <summary>
/// The oauth scopes.
/// </summary>
public enum OAuthScopes
{
/// <summary>
/// Scopes: bot applications.commands (Excluding applications.commands.permissions.update for now)
/// </summary>
BOT_DEFAULT = 0,
/// <summary>
/// Scopes: bot applications.commands
/// </summary>
BOT_MINIMAL = 1,
/// <summary>
/// Scopes: bot
/// </summary>
BOT_ONLY = 2,
/// <summary>
/// Scopes: identify email
/// </summary>
IDENTIFY_BASIC = 3,
/// <summary>
/// Scopes: identify email guilds connections
/// </summary>
IDENTIFY_EXTENDED = 4,
/// <summary>
/// Scopes: bot applications.commands applications.commands.permissions.update identify email guilds connections role_connections.write
/// </summary>
ALL = 5,
/// <summary>
/// Scopes: role_connections.write
/// </summary>
ROLE_CONNECTIONS_WRITE = 6
}
diff --git a/DisCatSharp/Enums/Permission.cs b/DisCatSharp/Enums/Permission.cs
index bc46993cf..ec1697e60 100644
--- a/DisCatSharp/Enums/Permission.cs
+++ b/DisCatSharp/Enums/Permission.cs
@@ -1,375 +1,375 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// Represents permission methods.
/// </summary>
public static class PermissionMethods
{
/// <summary>
/// Gets the full permissions enum (long).
/// </summary>
internal static Permissions FullPerms { get; } = (Permissions)4398046511103L;
/// <summary>
/// Calculates whether this permission set contains the given permission.
/// </summary>
/// <param name="p">The permissions to calculate from</param>
/// <param name="permission">permission you want to check</param>
/// <returns></returns>
public static bool HasPermission(this Permissions p, Permissions permission)
=> p.HasFlag(Permissions.Administrator) || (p & permission) == permission;
/// <summary>
/// Grants permissions.
/// </summary>
/// <param name="p">The permissions to add to.</param>
/// <param name="grant">Permission to add.</param>
/// <returns></returns>
public static Permissions Grant(this Permissions p, Permissions grant) => p | grant;
/// <summary>
/// Revokes permissions.
/// </summary>
/// <param name="p">The permissions to take from.</param>
/// <param name="revoke">Permission to take.</param>
/// <returns></returns>
public static Permissions Revoke(this Permissions p, Permissions revoke) => p & ~revoke;
}
/// <summary>
/// Whether a permission is allowed, denied or unset
/// </summary>
public enum PermissionLevel
{
/// <summary>
/// Said permission is Allowed
/// </summary>
Allowed,
/// <summary>
/// Said permission is Denied
/// </summary>
Denied,
/// <summary>
/// Said permission is Unset
/// </summary>
Unset
}
/// <summary>
/// Bitwise permission flags.
/// </summary>
[Flags]
public enum Permissions : long
{
/// <summary>
/// Indicates no permissions given.
/// This disallows users to run application command per default.
/// </summary>
[PermissionString("No permissions")]
None = 0,
/// <summary>
/// Indicates all permissions are granted
/// </summary>
[PermissionString("All permissions")]
All = 0b0011_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111,
/// <summary>
/// Allows creation of instant channel invites.
/// </summary>
[PermissionString("Create instant invites")]
CreateInstantInvite = 1L << 0,
/// <summary>
/// Allows kicking members.
/// </summary>
[PermissionString("Kick members")]
KickMembers = 1L << 1,
/// <summary>
/// Allows banning and unbanning members.
/// </summary>
[PermissionString("Ban members")]
BanMembers = 1L << 2,
/// <summary>
/// Enables full access on a given guild. This also overrides other permissions.
/// </summary>
[PermissionString("Administrator")]
Administrator = 1L << 3,
/// <summary>
/// Allows managing channels.
/// </summary>
[PermissionString("Manage channels")]
ManageChannels = 1L << 4,
/// <summary>
/// Allows managing the guild.
/// </summary>
[PermissionString("Manage guild")]
ManageGuild = 1L << 5,
/// <summary>
/// Allows adding reactions to messages.
/// </summary>
[PermissionString("Add reactions")]
AddReactions = 1L << 6,
/// <summary>
/// Allows viewing audit log entries.
/// </summary>
[PermissionString("View audit log")]
ViewAuditLog = 1L << 7,
/// <summary>
/// Allows the use of priority speaker.
/// </summary>
[PermissionString("Use priority speaker")]
PrioritySpeaker = 1L << 8,
/// <summary>
/// Allows the user to go live.
/// </summary>
[PermissionString("Allow stream")]
Stream = 1L << 9,
/// <summary>
/// Allows accessing text and voice channels. Disabling this permission hides channels.
/// </summary>
[PermissionString("Read messages")]
AccessChannels = 1L << 10,
/// <summary>
/// Allows sending messages (does not allow sending messages in threads).
/// </summary>
[PermissionString("Send messages")]
SendMessages = 1L << 11,
/// <summary>
/// Allows sending text-to-speech messages.
/// </summary>
[PermissionString("Send TTS messages")]
SendTtsMessages = 1L << 12,
/// <summary>
/// Allows managing messages of other users.
/// </summary>
[PermissionString("Manage messages")]
ManageMessages = 1L << 13,
/// <summary>
/// Allows embedding content in messages.
/// </summary>
[PermissionString("Use embeds")]
EmbedLinks = 1L << 14,
/// <summary>
/// Allows uploading files.
/// </summary>
[PermissionString("Attach files")]
AttachFiles = 1L << 15,
/// <summary>
/// Allows reading message history.
/// </summary>
[PermissionString("Read message history")]
ReadMessageHistory = 1L << 16,
/// <summary>
/// Allows using @everyone and @here mentions.
/// </summary>
[PermissionString("Mention everyone")]
MentionEveryone = 1L << 17,
/// <summary>
/// Allows using emojis from external servers, such as twitch or nitro emojis.
/// </summary>
[PermissionString("Use external emojis")]
UseExternalEmojis = 1L << 18,
/// <summary>
/// Allows to view guild insights.
/// </summary>
[PermissionString("View guild insights")]
ViewGuildInsights = 1L << 19,
/// <summary>
/// Allows connecting to voice chat.
/// </summary>
[PermissionString("Use voice chat")]
UseVoice = 1L << 20,
/// <summary>
/// Allows speaking in voice chat.
/// </summary>
[PermissionString("Speak")]
Speak = 1L << 21,
/// <summary>
/// Allows muting other members in voice chat.
/// </summary>
[PermissionString("Mute voice chat members")]
MuteMembers = 1L << 22,
/// <summary>
/// Allows deafening other members in voice chat.
/// </summary>
[PermissionString("Deafen voice chat members")]
DeafenMembers = 1L << 23,
/// <summary>
/// Allows moving voice chat members.
/// </summary>
[PermissionString("Move voice chat members")]
MoveMembers = 1L << 24,
/// <summary>
/// Allows using voice activation in voice chat. Revoking this will usage of push-to-talk.
/// </summary>
[PermissionString("Use voice activity detection")]
UseVoiceDetection = 1L << 25,
/// <summary>
/// Allows changing of own nickname.
/// </summary>
[PermissionString("Change own nickname")]
ChangeNickname = 1L << 26,
/// <summary>
/// Allows managing nicknames of other members.
/// </summary>
[PermissionString("Manage nicknames")]
ManageNicknames = 1L << 27,
/// <summary>
/// Allows managing roles in a guild.
/// </summary>
[PermissionString("Manage roles")]
ManageRoles = 1L << 28,
/// <summary>
/// Allows managing webhooks in a guild.
/// </summary>
[PermissionString("Manage webhooks")]
ManageWebhooks = 1L << 29,
/// <summary>
/// Allows managing guild emojis and stickers.
/// </summary>
[PermissionString("Manage emojis & stickers")]
ManageEmojisAndStickers = 1L << 30,
/// <summary>
/// Allows the user to use slash commands.
/// </summary>
[PermissionString("Use application commands")]
UseApplicationCommands = 1L << 31,
/// <summary>
/// Allows for requesting to speak in stage channels.
/// </summary>
[PermissionString("Request to speak")]
RequestToSpeak = 1L << 32,
/// <summary>
/// Allows managing guild events.
/// </summary>
[PermissionString("Manage Events")]
ManageEvents = 1L << 33,
/// <summary>
/// Allows for deleting and archiving threads, and viewing all private threads.
/// </summary>
[PermissionString("Manage Threads")]
ManageThreads = 1L << 34,
/// <summary>
/// Allows for creating threads.
/// </summary>
[PermissionString("Create Public Threads")]
CreatePublicThreads = 1L << 35,
/// <summary>
/// Allows for creating private threads.
/// </summary>
[PermissionString("Create Private Threads")]
CreatePrivateThreads = 1L << 36,
/// <summary>
/// Allows the usage of custom stickers from other servers.
/// </summary>
[PermissionString("Use external Stickers")]
UseExternalStickers = 1L << 37,
/// <summary>
/// Allows for sending messages in threads.
/// </summary>
[PermissionString("Send messages in Threads")]
SendMessagesInThreads = 1L << 38,
/// <summary>
/// Allows for launching activities (applications with the `EMBEDDED` flag) in a voice channel.
/// </summary>
[PermissionString("Start Embedded Activities")]
StartEmbeddedActivities = 1L << 39,
/// <summary>
/// Allows to perform limited moderation actions (timeout).
/// </summary>
[PermissionString("Moderate Members")]
ModerateMembers = 1L << 40,
/// <summary>
/// Allows to view creator monetization insights
/// </summary>
[PermissionString("Moderate Members")]
ViewCreatorMonetizationInsights = 1L << 41
}
/// <summary>
/// Defines a readable name for this permission.
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public sealed class PermissionStringAttribute : Attribute
{
/// <summary>
/// Gets the readable name for this permission.
/// </summary>
public string String { get; }
/// <summary>
/// Defines a readable name for this permission.
/// </summary>
/// <param name="str">Readable name for this permission.</param>
public PermissionStringAttribute(string str)
{
this.String = str;
}
}
diff --git a/DisCatSharp/Enums/TokenType.cs b/DisCatSharp/Enums/TokenType.cs
index cb24c09a6..8c6a55893 100644
--- a/DisCatSharp/Enums/TokenType.cs
+++ b/DisCatSharp/Enums/TokenType.cs
@@ -1,48 +1,48 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents the token type
/// </summary>
public enum TokenType
{
/// <summary>
/// User token type
/// </summary>
[Obsolete("Logging in with a user token may result in your account being terminated, and is therefore highly unrecommended." +
"\nIf anything goes wrong with this, we will not provide any support!", true)]
User = 0,
/// <summary>
/// Bot token type
/// </summary>
Bot = 1,
/// <summary>
/// Bearer token type (used for oAuth)
/// </summary>
Bearer = 2
}
diff --git a/DisCatSharp/Enums/User/ConnectionMetadataVisibilityType.cs b/DisCatSharp/Enums/User/ConnectionMetadataVisibilityType.cs
index 3a97ba018..46b3b2bd4 100644
--- a/DisCatSharp/Enums/User/ConnectionMetadataVisibilityType.cs
+++ b/DisCatSharp/Enums/User/ConnectionMetadataVisibilityType.cs
@@ -1,39 +1,39 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// The metadata visibility type of user account connections.
/// </summary>
public enum ConnectionMetadataVisibilityType : int
{
/// <summary>
/// This connections metadata is only visible to the owning user.
/// </summary>
None = 0,
/// <summary>
/// This connections metadata is visible to everyone.
/// </summary>
Everyone = 1
}
diff --git a/DisCatSharp/Enums/User/ConnectionVisibilityType.cs b/DisCatSharp/Enums/User/ConnectionVisibilityType.cs
index b2c00791a..b94dc29f1 100644
--- a/DisCatSharp/Enums/User/ConnectionVisibilityType.cs
+++ b/DisCatSharp/Enums/User/ConnectionVisibilityType.cs
@@ -1,39 +1,39 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// The visibility type of user account connections.
/// </summary>
public enum ConnectionVisibilityType : int
{
/// <summary>
/// This connection type is only visible to the owning user.
/// </summary>
None = 0,
/// <summary>
/// This connection is visible to everyone.
/// </summary>
Everyone = 1
}
diff --git a/DisCatSharp/Enums/User/PremiumType.cs b/DisCatSharp/Enums/User/PremiumType.cs
index d37b2b7cc..874199644 100644
--- a/DisCatSharp/Enums/User/PremiumType.cs
+++ b/DisCatSharp/Enums/User/PremiumType.cs
@@ -1,52 +1,52 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
/// <summary>
/// The type of Nitro subscription on a user's account.
/// </summary>
public enum PremiumType
{
/// <summary>
/// User does not have any perks.
/// </summary>
None = 0,
/// <summary>
/// Includes basic app perks like animated emojis and avatars.
/// </summary>
[Obsolete("Nitro Classic got replaced by Nitro Basic")]
NitroClassic = 1,
/// <summary>
/// Includes all app perks.
/// </summary>
Nitro = 2,
/// <summary>
/// Includes basic app perks.
/// </summary>
NitroBasic = 3
}
diff --git a/DisCatSharp/Enums/User/UserFlags.cs b/DisCatSharp/Enums/User/UserFlags.cs
index 644f84f64..7826d007d 100644
--- a/DisCatSharp/Enums/User/UserFlags.cs
+++ b/DisCatSharp/Enums/User/UserFlags.cs
@@ -1,211 +1,211 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents additional details of a users account.
/// </summary>
[Flags]
public enum UserFlags : long
{
/// <summary>
/// The user has no flags.
/// </summary>
None = 0,
/// <summary>
/// The user is a Discord employee.
/// </summary>
Staff = 1L << 0,
/// <summary>
/// The user is a Discord partner.
/// </summary>
Partner = 1L << 1,
/// <summary>
/// The user has the HypeSquad badge.
/// </summary>
HypeSquad = 1L << 2,
/// <summary>
/// The user reached the first bug hunter tier.
/// </summary>
BugHunterLevelOne = 1L << 3,
/// <summary>
/// The user has SMS recovery for 2FA enabled.
/// </summary>
MfaSms = 1L << 4,
/// <summary>
/// The user is marked as dismissed Nitro promotion
/// </summary>
PremiumPromoDismissed = 1L << 5,
/// <summary>
/// The user is a member of house bravery.
/// </summary>
HouseBravery = 1L << 6,
/// <summary>
/// The user is a member of house brilliance.
/// </summary>
HouseBrilliance = 1L << 7,
/// <summary>
/// The user is a member of house balance.
/// </summary>
HouseBalance = 1L << 8,
/// <summary>
/// The user has the early supporter badge.
/// </summary>
PremiumEarlySupporter = 1L << 9,
/// <summary>
/// User is a <see cref="Entities.DiscordTeam"/>.
/// </summary>
TeamPseudoUser = 1L << 10,
/// <summary>
/// User previously requested verification and/or partnership for a guild.
/// </summary>
InternalApplication = 1L << 11,
/// <summary>
/// Whether the user is an official system user.
/// </summary>
System = 1L << 12,
/// <summary>
/// Whether the user has unread system messages.
/// </summary>
HasUnreadUrgentMessages = 1L << 13,
/// <summary>
/// The user reached the second bug hunter tier.
/// </summary>
BugHunterLevelTwo = 1L << 14,
/// <summary>
/// The user has a pending deletion for being underage in DOB prompt.
/// </summary>
UnderageDeleted = 1L << 15,
/// <summary>
/// The user is a verified bot.
/// </summary>
VerifiedBot = 1L << 16,
/// <summary>
/// The user is a verified bot developer.
/// </summary>
VerifiedDeveloper = 1L << 17,
/// <summary>
/// The user is a discord certified moderator.
/// </summary>
CertifiedModerator = 1L << 18,
/// <summary>
/// The user is a bot and has set an interactions endpoint url.
/// </summary>
BotHttpInteractions = 1L << 19,
/// <summary>
/// The user is disabled for being a spammer.
/// </summary>
Spammer = 1L << 20,
/// <summary>
/// Nitro is disabled for user.
/// Used by discord staff instead of forcedNonPremium.
/// </summary>
DisablePremium = 1L << 21,
/// <summary>
/// User is an active developer.
/// Read more here: https://support-dev.discord.com/hc/articles/10113997751447.
/// </summary>
ActiveDeveloper = 1L << 22,
/// <summary>
/// Account has a high global ratelimit.
/// </summary>
HighGlobalRateLimit = 1L << 33,
/// <summary>
/// Account has been deleted.
/// </summary>
Deleted = 1L << 34,
/// <summary>
/// Account has been disabled for suspicious activity.
/// </summary>
DisabledSuspiciousActivity = 1L << 35,
/// <summary>
/// Account was deleted by the user.
/// </summary>
SelfDeleted = 1L << 36,
/// <summary>
/// The user has a premium discriminator.
/// </summary>
PremiumDiscriminator = 1L << 37,
/// <summary>
/// The user has used the desktop client
/// </summary>
UsedDesktopClient = 1L << 38,
/// <summary>
/// The user has used the web client
/// </summary>
UsedWebClient = 1L << 39,
/// <summary>
/// The user has used the mobile client
/// </summary>
UsedMobileClient = 1L << 40,
/// <summary>
/// The user is currently temporarily or permanently disabled.
/// </summary>
Disabled = 1L << 42,
/// <summary>
/// The user has a verified email.
/// </summary>
VerifiedEmail = 1L << 43,
/// <summary>
/// The user is currently quarantined.
/// The user can't start new dms and join servers.
/// The user has to appeal via https://dis.gd/appeal.
/// </summary>
Quarantined = 1L << 44
}
diff --git a/DisCatSharp/EventArgs/Application/ApplicationCommandEventArgs.cs b/DisCatSharp/EventArgs/Application/ApplicationCommandEventArgs.cs
index 18b302be8..6e0e3ef8d 100644
--- a/DisCatSharp/EventArgs/Application/ApplicationCommandEventArgs.cs
+++ b/DisCatSharp/EventArgs/Application/ApplicationCommandEventArgs.cs
@@ -1,50 +1,50 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for application command events.
/// </summary>
public sealed class ApplicationCommandEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the command that was modified.
/// </summary>
public DiscordApplicationCommand Command { get; internal set; }
/// <summary>
/// Gets the optional guild of the command.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="ApplicationCommandEventArgs"/> class.
/// </summary>
/// <param name="provider">The provider.</param>
public ApplicationCommandEventArgs(IServiceProvider provider) : base(provider)
{ }
}
diff --git a/DisCatSharp/EventArgs/Application/ApplicationCommandPermissionsUpdateEventArgs.cs b/DisCatSharp/EventArgs/Application/ApplicationCommandPermissionsUpdateEventArgs.cs
index 14cd55163..a28297ec9 100644
--- a/DisCatSharp/EventArgs/Application/ApplicationCommandPermissionsUpdateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Application/ApplicationCommandPermissionsUpdateEventArgs.cs
@@ -1,61 +1,61 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using DisCatSharp.Entities;
namespace DisCatSharp.EventArgs;
/// <summary>
/// Represents arguments for application command permissions update events.
/// </summary>
public sealed class ApplicationCommandPermissionsUpdateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the application command permissions.
/// </summary>
public List<DiscordApplicationCommandPermission> Permissions { get; internal set; }
/// <summary>
/// Gets the application command.
/// </summary>
public DiscordApplicationCommand Command { get; internal set; }
/// <summary>
/// Gets the application id.
/// </summary>
public ulong ApplicationId { get; internal set; }
/// <summary>
/// Gets the guild.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="ApplicationCommandPermissionsUpdateEventArgs"/> class.
/// </summary>
/// <param name="provider">The provider.</param>
public ApplicationCommandPermissionsUpdateEventArgs(IServiceProvider provider) : base(provider)
{ }
}
diff --git a/DisCatSharp/EventArgs/Application/GuildApplicationCommandCountEventArgs.cs b/DisCatSharp/EventArgs/Application/GuildApplicationCommandCountEventArgs.cs
index 3244d3c2d..b3ecc4bbd 100644
--- a/DisCatSharp/EventArgs/Application/GuildApplicationCommandCountEventArgs.cs
+++ b/DisCatSharp/EventArgs/Application/GuildApplicationCommandCountEventArgs.cs
@@ -1,60 +1,60 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for application command events.
/// </summary>
public sealed class GuildApplicationCommandCountEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the count of slash commands.
/// </summary>
public int SlashCommands { get; internal set; }
/// <summary>
/// Gets the count of user context menu commands.
/// </summary>
public int UserContextMenuCommands { get; internal set; }
/// <summary>
/// Gets the count of message context menu commands.
/// </summary>
public int MessageContextMenuCommands { get; internal set; }
/// <summary>
/// Gets the guild.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildApplicationCommandCountEventArgs"/> class.
/// </summary>
/// <param name="provider">The provider.</param>
public GuildApplicationCommandCountEventArgs(IServiceProvider provider) : base(provider)
{ }
}
diff --git a/DisCatSharp/EventArgs/Channel/ChannelCreateEventArgs.cs b/DisCatSharp/EventArgs/Channel/ChannelCreateEventArgs.cs
index 7ff599851..58dbe5290 100644
--- a/DisCatSharp/EventArgs/Channel/ChannelCreateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Channel/ChannelCreateEventArgs.cs
@@ -1,48 +1,48 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.ChannelCreated"/> event.
/// </summary>
public class ChannelCreateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the channel that was created.
/// </summary>
public DiscordChannel Channel { get; internal set; }
/// <summary>
/// Gets the guild in which the channel was created.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="ChannelCreateEventArgs"/> class.
/// </summary>
internal ChannelCreateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Channel/ChannelDeleteEventArgs.cs b/DisCatSharp/EventArgs/Channel/ChannelDeleteEventArgs.cs
index 55760513e..e4bbff1e3 100644
--- a/DisCatSharp/EventArgs/Channel/ChannelDeleteEventArgs.cs
+++ b/DisCatSharp/EventArgs/Channel/ChannelDeleteEventArgs.cs
@@ -1,48 +1,48 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.ChannelDeleted"/> event.
/// </summary>
public class ChannelDeleteEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the channel that was deleted.
/// </summary>
public DiscordChannel Channel { get; internal set; }
/// <summary>
/// Gets the guild this channel belonged to.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="ChannelDeleteEventArgs"/> class.
/// </summary>
internal ChannelDeleteEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Channel/ChannelPinsUpdateEventArgs.cs b/DisCatSharp/EventArgs/Channel/ChannelPinsUpdateEventArgs.cs
index feb635e27..0d983caca 100644
--- a/DisCatSharp/EventArgs/Channel/ChannelPinsUpdateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Channel/ChannelPinsUpdateEventArgs.cs
@@ -1,53 +1,53 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.ChannelPinsUpdated"/> event.
/// </summary>
public class ChannelPinsUpdateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the guild in which the update occurred.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the channel in which the update occurred.
/// </summary>
public DiscordChannel Channel { get; internal set; }
/// <summary>
/// Gets the timestamp of the latest pin.
/// </summary>
public DateTimeOffset? LastPinTimestamp { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="ChannelPinsUpdateEventArgs"/> class.
/// </summary>
internal ChannelPinsUpdateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Channel/ChannelUpdateEventArgs.cs b/DisCatSharp/EventArgs/Channel/ChannelUpdateEventArgs.cs
index 978ec8b78..4a9412e55 100644
--- a/DisCatSharp/EventArgs/Channel/ChannelUpdateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Channel/ChannelUpdateEventArgs.cs
@@ -1,53 +1,53 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.ChannelUpdated"/> event.
/// </summary>
public class ChannelUpdateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the post-update channel.
/// </summary>
public DiscordChannel ChannelAfter { get; internal set; }
/// <summary>
/// Gets the pre-update channel.
/// </summary>
public DiscordChannel ChannelBefore { get; internal set; }
/// <summary>
/// Gets the guild in which the update occurred.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="ChannelUpdateEventArgs"/> class.
/// </summary>
internal ChannelUpdateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Channel/DMChannelDeleteEventArgs.cs b/DisCatSharp/EventArgs/Channel/DMChannelDeleteEventArgs.cs
index 193d3ae42..c0766946b 100644
--- a/DisCatSharp/EventArgs/Channel/DMChannelDeleteEventArgs.cs
+++ b/DisCatSharp/EventArgs/Channel/DMChannelDeleteEventArgs.cs
@@ -1,43 +1,43 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.DmChannelDeleted"/> event.
/// </summary>
public class DmChannelDeleteEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the direct message channel that was deleted.
/// </summary>
public DiscordDmChannel Channel { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="DmChannelDeleteEventArgs"/> class.
/// </summary>
internal DmChannelDeleteEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/ClientErrorEventArgs.cs b/DisCatSharp/EventArgs/ClientErrorEventArgs.cs
index 6feba8d81..4d44fbd43 100644
--- a/DisCatSharp/EventArgs/ClientErrorEventArgs.cs
+++ b/DisCatSharp/EventArgs/ClientErrorEventArgs.cs
@@ -1,46 +1,46 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.EventArgs;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.ClientErrored"/> event.
/// </summary>
public class ClientErrorEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the exception thrown by the client.
/// </summary>
public Exception Exception { get; internal set; }
/// <summary>
/// Gets the name of the event that threw the exception.
/// </summary>
public string EventName { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="ClientErrorEventArgs"/> class.
/// </summary>
internal ClientErrorEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/DiscordEventArgs.cs b/DisCatSharp/EventArgs/DiscordEventArgs.cs
index a1b4cb987..5b50f4189 100644
--- a/DisCatSharp/EventArgs/DiscordEventArgs.cs
+++ b/DisCatSharp/EventArgs/DiscordEventArgs.cs
@@ -1,54 +1,54 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using DisCatSharp.Common.Utilities;
using Microsoft.Extensions.DependencyInjection;
namespace DisCatSharp.EventArgs;
// Note: this might seem useless, but should we ever need to add a common property or method to all event arg
// classes, it would be useful to already have a base for all of it.
/// <summary>
/// Common base for all other <see cref="DiscordClient"/>-related event argument classes.
/// </summary>
public abstract class DiscordEventArgs : AsyncEventArgs
{
/// <summary>
/// <para>Gets the service provider.</para>
/// <para>This allows passing data around without resorting to static members.</para>
/// <para>Defaults to an empty service provider.</para>
/// </summary>
public IServiceProvider ServiceProvider { get; internal set; } = new ServiceCollection().BuildServiceProvider(true);
/// <summary>
/// Initializes a new instance of the <see cref="DiscordEventArgs"/> class.
/// </summary>
protected DiscordEventArgs(IServiceProvider provider)
{
if (provider != null)
this.ServiceProvider = provider.CreateScope().ServiceProvider;
}
}
diff --git a/DisCatSharp/EventArgs/EmbeddedActivityUpdateEventArgs.cs b/DisCatSharp/EventArgs/EmbeddedActivityUpdateEventArgs.cs
index 6422ff2e6..8d8dad1f2 100644
--- a/DisCatSharp/EventArgs/EmbeddedActivityUpdateEventArgs.cs
+++ b/DisCatSharp/EventArgs/EmbeddedActivityUpdateEventArgs.cs
@@ -1,64 +1,64 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using DisCatSharp.Entities;
namespace DisCatSharp.EventArgs;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.EmbeddedActivityUpdated"/> event.
/// </summary>
public class EmbeddedActivityUpdateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the guild.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the channel.
/// </summary>
public DiscordChannel Channel { get; internal set; }
/// <summary>
/// Gets the embedded activity.
/// </summary>
public DiscordActivity EmbeddedActivityBefore { get; internal set; }
/// <summary>
/// Gets the embedded activity.
/// </summary>
public DiscordActivity EmbeddedActivityAfter { get; internal set; }
/// <summary>
/// Gets the users in the activity.
/// </summary>
public IReadOnlyList<DiscordMember> Users { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="EmbeddedActivityUpdateEventArgs"/> class.
/// </summary>
internal EmbeddedActivityUpdateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/Automod/AutomodActionExecutedEventArgs.cs b/DisCatSharp/EventArgs/Guild/Automod/AutomodActionExecutedEventArgs.cs
index 8feb2348b..423f878fc 100644
--- a/DisCatSharp/EventArgs/Guild/Automod/AutomodActionExecutedEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/Automod/AutomodActionExecutedEventArgs.cs
@@ -1,111 +1,111 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
namespace DisCatSharp.EventArgs
{
/// <summary>
/// Represents arguments for <see cref="DiscordClient.AutomodActionExecuted"/> event.
/// </summary>
public class AutomodActionExecutedEventArgs : DiscordEventArgs
{
/// <summary>
/// The guild associated with this event.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// The action that was executed.
/// </summary>
public AutomodAction Action { get; internal set; }
/// <summary>
/// The id of the rule the action belongs to.
/// </summary>
public ulong RuleId { get; internal set; }
/// <summary>
/// The type of trigger of the rule which was executed.
/// </summary>
public AutomodTriggerType TriggerType { get; internal set; }
/// <summary>
/// The member which caused this event.
/// </summary>
public DiscordMember Member
=> this.Guild.Members.TryGetValue(this.UserId, out var member) ? member : this.Guild.GetMemberAsync(this.UserId, true).Result;
/// <summary>
/// The user id which caused this event.
/// </summary>
public ulong UserId { get; internal set; }
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public DiscordChannel? Channel
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
=> this.ChannelId.HasValue ? this.Guild.GetChannel(this.ChannelId.Value) : null;
/// <summary>
/// Fall-back channel id this event happened in.
/// </summary>
public ulong? ChannelId { get; internal set; }
/// <summary>
/// The id of any user message the content belongs to.
/// This will not exist if the message was blocked or content was not part of message.
/// </summary>
public ulong? MessageId { get; internal set; }
/// <summary>
/// The id of any system auto moderation messages posted as a result of this action.
/// This will not exist if the event doesn't correspond to an action with type SendAlertMessage.
/// </summary>
public ulong? AlertMessageId { get; internal set; }
/// <summary>
/// The user-generated text content.
/// </summary>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public string? MessageContent { get; internal set; }
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
/// <summary>
/// The word or phrase configured in the rule that triggered this.
/// </summary>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public string? MatchedKeyword { get; internal set; }
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
/// <summary>
/// The substring in the content which triggered the rule.
/// </summary>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public string? MatchedContent { get; internal set; }
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public AutomodActionExecutedEventArgs(IServiceProvider provider) : base(provider) { }
}
}
diff --git a/DisCatSharp/EventArgs/Guild/Automod/AutomodCreateRuleEventArgs.cs b/DisCatSharp/EventArgs/Guild/Automod/AutomodCreateRuleEventArgs.cs
index b09d629d5..5a800325f 100644
--- a/DisCatSharp/EventArgs/Guild/Automod/AutomodCreateRuleEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/Automod/AutomodCreateRuleEventArgs.cs
@@ -1,44 +1,44 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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
{
/// <summary>
/// Represents arguments for <see cref="DiscordClient.AutomodRuleCreated"/> event.
/// </summary>
public class AutomodRuleCreateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the rule that has been created.
/// </summary>
public AutomodRule Rule { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="AutomodRuleCreateEventArgs"/> class.
/// </summary>
public AutomodRuleCreateEventArgs(IServiceProvider provider) : base(provider) { }
}
}
diff --git a/DisCatSharp/EventArgs/Guild/Automod/AutomodDeleteRuleEventArgs.cs b/DisCatSharp/EventArgs/Guild/Automod/AutomodDeleteRuleEventArgs.cs
index 8802881df..90aa8e5c1 100644
--- a/DisCatSharp/EventArgs/Guild/Automod/AutomodDeleteRuleEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/Automod/AutomodDeleteRuleEventArgs.cs
@@ -1,41 +1,41 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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
{
/// <summary>
/// Represents arguments for <see cref="DiscordClient.AutomodRuleDeleted"/> event.
/// </summary>
public class AutomodRuleDeleteEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the rule that has been deleted.
/// </summary>
public AutomodRule Rule { get; internal set; }
public AutomodRuleDeleteEventArgs(IServiceProvider provider) : base(provider) { }
}
}
diff --git a/DisCatSharp/EventArgs/Guild/Automod/AutomodUpdateRuleEventArgs.cs b/DisCatSharp/EventArgs/Guild/Automod/AutomodUpdateRuleEventArgs.cs
index 7404153ae..b11e4ee55 100644
--- a/DisCatSharp/EventArgs/Guild/Automod/AutomodUpdateRuleEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/Automod/AutomodUpdateRuleEventArgs.cs
@@ -1,41 +1,41 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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
{
/// <summary>
/// Represents arguments for <see cref="DiscordClient.AutomodRuleUpdated"/> event.
/// </summary>
public class AutomodRuleUpdateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the rule that has been updated.
/// </summary>
public AutomodRule Rule { get; internal set; }
public AutomodRuleUpdateEventArgs(IServiceProvider provider) : base(provider) { }
}
}
diff --git a/DisCatSharp/EventArgs/Guild/Ban/GuildBanAddEventArgs.cs b/DisCatSharp/EventArgs/Guild/Ban/GuildBanAddEventArgs.cs
index 803f719b5..a7e766781 100644
--- a/DisCatSharp/EventArgs/Guild/Ban/GuildBanAddEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/Ban/GuildBanAddEventArgs.cs
@@ -1,48 +1,48 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildBanAdded"/> event.
/// </summary>
public class GuildBanAddEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the member that was banned.
/// </summary>
public DiscordMember Member { get; internal set; }
/// <summary>
/// Gets the guild this member was banned in.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildBanAddEventArgs"/> class.
/// </summary>
internal GuildBanAddEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/Ban/GuildBanRemoveEventArgs.cs b/DisCatSharp/EventArgs/Guild/Ban/GuildBanRemoveEventArgs.cs
index 5a9953bc6..2d275e291 100644
--- a/DisCatSharp/EventArgs/Guild/Ban/GuildBanRemoveEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/Ban/GuildBanRemoveEventArgs.cs
@@ -1,48 +1,48 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildBanRemoved"/> event.
/// </summary>
public class GuildBanRemoveEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the member that just got unbanned.
/// </summary>
public DiscordMember Member { get; internal set; }
/// <summary>
/// Gets the guild this member was unbanned in.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildBanRemoveEventArgs"/> class.
/// </summary>
internal GuildBanRemoveEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/GuildAuditLogEntryCreateEventArgs.cs b/DisCatSharp/EventArgs/Guild/GuildAuditLogEntryCreateEventArgs.cs
index 0e005762a..e6bb3abf9 100644
--- a/DisCatSharp/EventArgs/Guild/GuildAuditLogEntryCreateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/GuildAuditLogEntryCreateEventArgs.cs
@@ -1,49 +1,49 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildAuditLogEntryCreated"/> event.
/// </summary>
public class GuildAuditLogEntryCreateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the audit log entry.
/// Cast to correct type by action type.
/// </summary>
public DiscordAuditLogEntry AuditLogEntry { get; internal set; }
-
+
/// <summary>
/// Gets the guild in which the update occurred.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildAuditLogEntryCreateEventArgs"/> class.
/// </summary>
internal GuildAuditLogEntryCreateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/GuildCreateEventArgs.cs b/DisCatSharp/EventArgs/Guild/GuildCreateEventArgs.cs
index fdc0ae163..d5dfe39c7 100644
--- a/DisCatSharp/EventArgs/Guild/GuildCreateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/GuildCreateEventArgs.cs
@@ -1,43 +1,43 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildCreated"/> event.
/// </summary>
public class GuildCreateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the guild that was created.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildCreateEventArgs"/> class.
/// </summary>
internal GuildCreateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/GuildDeleteEventArgs.cs b/DisCatSharp/EventArgs/Guild/GuildDeleteEventArgs.cs
index 27828abf5..0fa52ef04 100644
--- a/DisCatSharp/EventArgs/Guild/GuildDeleteEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/GuildDeleteEventArgs.cs
@@ -1,48 +1,48 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildDeleted"/> event.
/// </summary>
public class GuildDeleteEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the guild that was deleted.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets whether the guild is unavailable or not.
/// </summary>
public bool Unavailable { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildDeleteEventArgs"/> class.
/// </summary>
internal GuildDeleteEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/GuildDownloadCompletedEventArgs.cs b/DisCatSharp/EventArgs/Guild/GuildDownloadCompletedEventArgs.cs
index 240a8d07a..6dafa3618 100644
--- a/DisCatSharp/EventArgs/Guild/GuildDownloadCompletedEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/GuildDownloadCompletedEventArgs.cs
@@ -1,50 +1,50 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using DisCatSharp.Entities;
namespace DisCatSharp.EventArgs;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildDownloadCompleted"/> event.
/// </summary>
public class GuildDownloadCompletedEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the dictionary of guilds that just finished downloading.
/// </summary>
public IReadOnlyDictionary<ulong, DiscordGuild> Guilds { get; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildDownloadCompletedEventArgs"/> class.
/// </summary>
/// <param name="guilds">The guilds.</param>
/// <param name="provider">Service provider.</param>
internal GuildDownloadCompletedEventArgs(IReadOnlyDictionary<ulong, DiscordGuild> guilds, IServiceProvider provider)
: base(provider)
{
this.Guilds = guilds;
}
}
diff --git a/DisCatSharp/EventArgs/Guild/GuildEmojisUpdateEventArgs.cs b/DisCatSharp/EventArgs/Guild/GuildEmojisUpdateEventArgs.cs
index a6d82c910..e9ea2f545 100644
--- a/DisCatSharp/EventArgs/Guild/GuildEmojisUpdateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/GuildEmojisUpdateEventArgs.cs
@@ -1,54 +1,54 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using DisCatSharp.Entities;
namespace DisCatSharp.EventArgs;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildEmojisUpdated"/> event.
/// </summary>
public class GuildEmojisUpdateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the list of emojis after the change.
/// </summary>
public IReadOnlyDictionary<ulong, DiscordEmoji> EmojisAfter { get; internal set; }
/// <summary>
/// Gets the list of emojis before the change.
/// </summary>
public IReadOnlyDictionary<ulong, DiscordEmoji> EmojisBefore { get; internal set; }
/// <summary>
/// Gets the guild in which the update occurred.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildEmojisUpdateEventArgs"/> class.
/// </summary>
internal GuildEmojisUpdateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/GuildIntegrationsUpdateEventArgs.cs b/DisCatSharp/EventArgs/Guild/GuildIntegrationsUpdateEventArgs.cs
index 0b0e29183..52dceb21c 100644
--- a/DisCatSharp/EventArgs/Guild/GuildIntegrationsUpdateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/GuildIntegrationsUpdateEventArgs.cs
@@ -1,43 +1,43 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildIntegrationsUpdated"/> event.
/// </summary>
public class GuildIntegrationsUpdateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the guild that had its integrations updated.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildIntegrationsUpdateEventArgs"/> class.
/// </summary>
internal GuildIntegrationsUpdateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/GuildStickersUpdateEventArgs.cs b/DisCatSharp/EventArgs/Guild/GuildStickersUpdateEventArgs.cs
index 8ff51ce7b..7e01b67e2 100644
--- a/DisCatSharp/EventArgs/Guild/GuildStickersUpdateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/GuildStickersUpdateEventArgs.cs
@@ -1,54 +1,54 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using DisCatSharp.Entities;
namespace DisCatSharp.EventArgs;
/// <summary>
/// Represents event args for the <see cref="DiscordClient.GuildStickersUpdated"/> event.
/// </summary>
public class GuildStickersUpdateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the list of stickers after the change.
/// </summary>
public IReadOnlyDictionary<ulong, DiscordSticker> StickersAfter { get; internal set; }
/// <summary>
/// Gets the list of stickers before the change.
/// </summary>
public IReadOnlyDictionary<ulong, DiscordSticker> StickersBefore { get; internal set; }
/// <summary>
/// Gets the guild in which the update occurred.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildStickersUpdateEventArgs"/> class.
/// </summary>
internal GuildStickersUpdateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/GuildUpdateEventArgs.cs b/DisCatSharp/EventArgs/Guild/GuildUpdateEventArgs.cs
index b900030b6..0b1af7650 100644
--- a/DisCatSharp/EventArgs/Guild/GuildUpdateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/GuildUpdateEventArgs.cs
@@ -1,48 +1,48 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildUpdated"/> event.
/// </summary>
public class GuildUpdateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the guild before it was updated.
/// </summary>
public DiscordGuild GuildBefore { get; internal set; }
/// <summary>
/// Gets the guild after it was updated.
/// </summary>
public DiscordGuild GuildAfter { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildUpdateEventArgs"/> class.
/// </summary>
internal GuildUpdateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/Integration/GuildIntegrationCreateEventArgs.cs b/DisCatSharp/EventArgs/Guild/Integration/GuildIntegrationCreateEventArgs.cs
index f34a11b9a..ac123a636 100644
--- a/DisCatSharp/EventArgs/Guild/Integration/GuildIntegrationCreateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/Integration/GuildIntegrationCreateEventArgs.cs
@@ -1,49 +1,49 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildIntegrationCreated"/> event.
/// </summary>
public class GuildIntegrationCreateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the integration that was created.
/// </summary>
///
public DiscordIntegration Integration { get; internal set; }
/// <summary>
/// Gets the guild where the integration was created.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildIntegrationCreateEventArgs"/> class.
/// </summary>
internal GuildIntegrationCreateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/Integration/GuildIntegrationDeleteEventArgs.cs b/DisCatSharp/EventArgs/Guild/Integration/GuildIntegrationDeleteEventArgs.cs
index 7bb9b22c8..b5c8ceec6 100644
--- a/DisCatSharp/EventArgs/Guild/Integration/GuildIntegrationDeleteEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/Integration/GuildIntegrationDeleteEventArgs.cs
@@ -1,54 +1,54 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildIntegrationDeleted"/> event.
/// </summary>
public class GuildIntegrationDeleteEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the integration id which where deleted.
/// </summary>
///
public ulong IntegrationId { get; internal set; }
/// <summary>
/// Gets the guild where the integration which where deleted.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the application id of the integration which where deleted.
/// </summary>
public ulong? ApplicationId { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildIntegrationDeleteEventArgs"/> class.
/// </summary>
internal GuildIntegrationDeleteEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/Integration/GuildIntegrationUpdateEventArgs.cs b/DisCatSharp/EventArgs/Guild/Integration/GuildIntegrationUpdateEventArgs.cs
index 6eb91cffc..50307a829 100644
--- a/DisCatSharp/EventArgs/Guild/Integration/GuildIntegrationUpdateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/Integration/GuildIntegrationUpdateEventArgs.cs
@@ -1,49 +1,49 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildIntegrationUpdated"/> event.
/// </summary>
public class GuildIntegrationUpdateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the integration that was updated.
/// </summary>
///
public DiscordIntegration Integration { get; internal set; }
/// <summary>
/// Gets the guild where the integration was updated.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildIntegrationUpdateEventArgs"/> class.
/// </summary>
internal GuildIntegrationUpdateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/Member/GuildMemberAddEventArgs.cs b/DisCatSharp/EventArgs/Guild/Member/GuildMemberAddEventArgs.cs
index 715e94084..b42dc87dd 100644
--- a/DisCatSharp/EventArgs/Guild/Member/GuildMemberAddEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/Member/GuildMemberAddEventArgs.cs
@@ -1,48 +1,48 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildMemberAdded"/> event.
/// </summary>
public class GuildMemberAddEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the member that was added.
/// </summary>
public DiscordMember Member { get; internal set; }
/// <summary>
/// Gets the guild the member was added to.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildMemberAddEventArgs"/> class.
/// </summary>
internal GuildMemberAddEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/Member/GuildMemberRemoveEventArgs.cs b/DisCatSharp/EventArgs/Guild/Member/GuildMemberRemoveEventArgs.cs
index af469b35b..fc8532b75 100644
--- a/DisCatSharp/EventArgs/Guild/Member/GuildMemberRemoveEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/Member/GuildMemberRemoveEventArgs.cs
@@ -1,48 +1,48 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildMemberRemoved"/> event.
/// </summary>
public class GuildMemberRemoveEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the guild the member was removed from.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the member that was removed.
/// </summary>
public DiscordMember Member { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildMemberRemoveEventArgs"/> class.
/// </summary>
internal GuildMemberRemoveEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/Member/GuildMemberUpdateEventArgs.cs b/DisCatSharp/EventArgs/Guild/Member/GuildMemberUpdateEventArgs.cs
index 8626b4018..964249430 100644
--- a/DisCatSharp/EventArgs/Guild/Member/GuildMemberUpdateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/Member/GuildMemberUpdateEventArgs.cs
@@ -1,113 +1,113 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.Enums;
using DisCatSharp.Net;
namespace DisCatSharp.EventArgs;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildMemberUpdated"/> event.
/// </summary>
public class GuildMemberUpdateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the guild in which the update occurred.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets a collection containing post-update roles.
/// </summary>
public IReadOnlyList<DiscordRole> RolesAfter { get; internal set; }
/// <summary>
/// Gets a collection containing pre-update roles.
/// </summary>
public IReadOnlyList<DiscordRole> RolesBefore { get; internal set; }
/// <summary>
/// Gets the member's new nickname.
/// </summary>
public string NicknameAfter { get; internal set; }
/// <summary>
/// Gets the member's old nickname.
/// </summary>
public string NicknameBefore { get; internal set; }
/// <summary>
/// Gets whether the member had passed membership screening before the update.
/// </summary>
public bool? PendingBefore { get; internal set; }
/// <summary>
/// Gets whether the member had passed membership screening after the update.
/// </summary>
public bool? PendingAfter { get; internal set; }
/// <summary>
/// Gets whether the member is timed out before the update.
/// </summary>
public DateTimeOffset? TimeoutBefore { get; internal set; }
/// <summary>
/// Gets whether the member is timed out after the update.
/// </summary>
public DateTimeOffset? TimeoutAfter { get; internal set; }
/// <summary>
/// Gets the member that was updated.
/// </summary>
public DiscordMember Member { get; internal set; }
public virtual string GuildAvatarHashBefore { get; internal set; }
public string GuildAvatarUrlBefore
=> string.IsNullOrWhiteSpace(this.GuildAvatarHashBefore) ? null : $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.GUILDS}/{this.Guild.Id.ToString(CultureInfo.InvariantCulture)}{Endpoints.USERS}/{this.Member.Id.ToString(CultureInfo.InvariantCulture)}{Endpoints.AVATARS}/{this.GuildAvatarHashBefore}.{(this.GuildAvatarHashBefore.StartsWith("a_") ? "gif" : "png")}?size=1024";
public virtual string GuildAvatarHashAfter { get; internal set; }
public string GuildAvatarUrlAfter
=> string.IsNullOrWhiteSpace(this.GuildAvatarHashAfter) ? null : $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.GUILDS}/{this.Guild.Id.ToString(CultureInfo.InvariantCulture)}{Endpoints.USERS}/{this.Member.Id.ToString(CultureInfo.InvariantCulture)}{Endpoints.AVATARS}/{this.GuildAvatarHashAfter}.{(this.GuildAvatarHashAfter.StartsWith("a_") ? "gif" : "png")}?size=1024";
public virtual string AvatarHashBefore { get; internal set; }
public string AvatarUrlBefore
=> string.IsNullOrWhiteSpace(this.AvatarHashBefore) ? null : $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.GUILDS}/{this.Guild.Id.ToString(CultureInfo.InvariantCulture)}{Endpoints.USERS}/{this.Member.Id.ToString(CultureInfo.InvariantCulture)}{Endpoints.AVATARS}/{this.AvatarHashBefore}.{(this.AvatarHashBefore.StartsWith("a_") ? "gif" : "png")}?size=1024";
public virtual string AvatarHashAfter { get; internal set; }
public string AvatarUrlAfter
=> string.IsNullOrWhiteSpace(this.AvatarHashAfter) ? null : $"{DiscordDomain.GetDomain(CoreDomain.DiscordCdn).Url}{Endpoints.GUILDS}/{this.Guild.Id.ToString(CultureInfo.InvariantCulture)}{Endpoints.USERS}/{this.Member.Id.ToString(CultureInfo.InvariantCulture)}{Endpoints.AVATARS}/{this.AvatarHashAfter}.{(this.AvatarHashAfter.StartsWith("a_") ? "gif" : "png")}?size=1024";
/// <summary>
/// Initializes a new instance of the <see cref="GuildMemberUpdateEventArgs"/> class.
/// </summary>
internal GuildMemberUpdateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/Member/GuildMembersChunkEventArgs.cs b/DisCatSharp/EventArgs/Guild/Member/GuildMembersChunkEventArgs.cs
index 3c9e7b37b..d949b5313 100644
--- a/DisCatSharp/EventArgs/Guild/Member/GuildMembersChunkEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/Member/GuildMembersChunkEventArgs.cs
@@ -1,74 +1,74 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using DisCatSharp.Entities;
namespace DisCatSharp.EventArgs;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildMembersChunked"/> event.
/// </summary>
public class GuildMembersChunkEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the guild that requested this chunk.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the collection of members returned from this chunk.
/// </summary>
public IReadOnlyCollection<DiscordMember> Members { get; internal set; }
/// <summary>
/// Gets the current chunk index from the response.
/// </summary>
public int ChunkIndex { get; internal set; }
/// <summary>
/// Gets the total amount of chunks for the request.
/// </summary>
public int ChunkCount { get; internal set; }
/// <summary>
/// Gets the collection of presences returned from this chunk, if specified.
/// </summary>
public IReadOnlyCollection<DiscordPresence> Presences { get; internal set; }
/// <summary>
/// Gets the returned Ids that were not found in the chunk, if specified.
/// </summary>
public IReadOnlyCollection<ulong> NotFound { get; internal set; }
/// <summary>
/// Gets the unique string used to identify the request, if specified.
/// </summary>
public string Nonce { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildMembersChunkEventArgs"/> class.
/// </summary>
internal GuildMembersChunkEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/Role/GuildRoleCreateEventArgs.cs b/DisCatSharp/EventArgs/Guild/Role/GuildRoleCreateEventArgs.cs
index 67e951f7f..a15f8af12 100644
--- a/DisCatSharp/EventArgs/Guild/Role/GuildRoleCreateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/Role/GuildRoleCreateEventArgs.cs
@@ -1,48 +1,48 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildRoleCreated"/> event.
/// </summary>
public class GuildRoleCreateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the guild in which the role was created.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the role that was created.
/// </summary>
public DiscordRole Role { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildRoleCreateEventArgs"/> class.
/// </summary>
internal GuildRoleCreateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/Role/GuildRoleDeleteEventArgs.cs b/DisCatSharp/EventArgs/Guild/Role/GuildRoleDeleteEventArgs.cs
index a510075fc..bc31dc374 100644
--- a/DisCatSharp/EventArgs/Guild/Role/GuildRoleDeleteEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/Role/GuildRoleDeleteEventArgs.cs
@@ -1,48 +1,48 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildRoleDeleted"/> event.
/// </summary>
public class GuildRoleDeleteEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the guild in which the role was deleted.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the role that was deleted.
/// </summary>
public DiscordRole Role { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildRoleDeleteEventArgs"/> class.
/// </summary>
internal GuildRoleDeleteEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/Role/GuildRoleUpdateEventArgs.cs b/DisCatSharp/EventArgs/Guild/Role/GuildRoleUpdateEventArgs.cs
index b7ea253f3..d2744c1df 100644
--- a/DisCatSharp/EventArgs/Guild/Role/GuildRoleUpdateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/Role/GuildRoleUpdateEventArgs.cs
@@ -1,53 +1,53 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildRoleUpdated"/> event.
/// </summary>
public class GuildRoleUpdateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the guild in which the update occurred.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the post-update role.
/// </summary>
public DiscordRole RoleAfter { get; internal set; }
/// <summary>
/// Gets the pre-update role.
/// </summary>
public DiscordRole RoleBefore { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildRoleUpdateEventArgs"/> class.
/// </summary>
internal GuildRoleUpdateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/ScheduledEvent/GuildScheduledEventCreateEventArgs.cs b/DisCatSharp/EventArgs/Guild/ScheduledEvent/GuildScheduledEventCreateEventArgs.cs
index c0bdb1020..95320398f 100644
--- a/DisCatSharp/EventArgs/Guild/ScheduledEvent/GuildScheduledEventCreateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/ScheduledEvent/GuildScheduledEventCreateEventArgs.cs
@@ -1,48 +1,48 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildScheduledEventCreated"/> event.
/// </summary>
public class GuildScheduledEventCreateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the scheduled event that was created.
/// </summary>
public DiscordScheduledEvent ScheduledEvent { get; internal set; }
/// <summary>
/// Gets the guild in which the scheduled event was created.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildScheduledEventCreateEventArgs"/> class.
/// </summary>
internal GuildScheduledEventCreateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/ScheduledEvent/GuildScheduledEventDeleteEventArgs.cs b/DisCatSharp/EventArgs/Guild/ScheduledEvent/GuildScheduledEventDeleteEventArgs.cs
index 9015b0efe..428d51c29 100644
--- a/DisCatSharp/EventArgs/Guild/ScheduledEvent/GuildScheduledEventDeleteEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/ScheduledEvent/GuildScheduledEventDeleteEventArgs.cs
@@ -1,55 +1,55 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
namespace DisCatSharp.EventArgs;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildScheduledEventDeleted"/> event.
/// </summary>
public class GuildScheduledEventDeleteEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the scheduled event that was deleted.
/// </summary>
public DiscordScheduledEvent ScheduledEvent { get; internal set; }
/// <summary>
/// Gets the reason of deletion for the scheduled event.
/// Important to determine why and how it was deleted.
/// </summary>
public ScheduledEventStatus Reason { get; internal set; }
/// <summary>
/// Gets the guild in which the scheduled event was deleted.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildScheduledEventDeleteEventArgs"/> class.
/// </summary>
internal GuildScheduledEventDeleteEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/ScheduledEvent/GuildScheduledEventUpdateEventArgs.cs b/DisCatSharp/EventArgs/Guild/ScheduledEvent/GuildScheduledEventUpdateEventArgs.cs
index 028da90bc..856a84b57 100644
--- a/DisCatSharp/EventArgs/Guild/ScheduledEvent/GuildScheduledEventUpdateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/ScheduledEvent/GuildScheduledEventUpdateEventArgs.cs
@@ -1,53 +1,53 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildScheduledEventUpdated"/> event.
/// </summary>
public class GuildScheduledEventUpdateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the scheduled event that was updated.
/// </summary>
public DiscordScheduledEvent ScheduledEventAfter { get; internal set; }
/// <summary>
/// Gets the old scheduled event that was updated.
/// </summary>
public DiscordScheduledEvent ScheduledEventBefore { get; internal set; }
/// <summary>
/// Gets the guild in which the scheduled event was updated.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildScheduledEventUpdateEventArgs"/> class.
/// </summary>
internal GuildScheduledEventUpdateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/ScheduledEvent/GuildScheduledEventUserAddEventArgs.cs b/DisCatSharp/EventArgs/Guild/ScheduledEvent/GuildScheduledEventUserAddEventArgs.cs
index 44cf4bd00..db8dc502c 100644
--- a/DisCatSharp/EventArgs/Guild/ScheduledEvent/GuildScheduledEventUserAddEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/ScheduledEvent/GuildScheduledEventUserAddEventArgs.cs
@@ -1,58 +1,58 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildScheduledEventUserAdded"/> event.
/// </summary>
public class GuildScheduledEventUserAddEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the scheduled event.
/// </summary>
public DiscordScheduledEvent ScheduledEvent { get; internal set; }
/// <summary>
/// Gets the guild.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the user which has subscribed to this scheduled event.
/// </summary>
public DiscordUser User { get; internal set; }
/// <summary>
/// Gets the member which has subscribed to this scheduled event.
/// </summary>
public DiscordMember Member { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildScheduledEventUserAddEventArgs"/> class.
/// </summary>
internal GuildScheduledEventUserAddEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/ScheduledEvent/GuildScheduledEventUserRemoveEventArgs.cs b/DisCatSharp/EventArgs/Guild/ScheduledEvent/GuildScheduledEventUserRemoveEventArgs.cs
index 362a8fba8..c4c3a139e 100644
--- a/DisCatSharp/EventArgs/Guild/ScheduledEvent/GuildScheduledEventUserRemoveEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/ScheduledEvent/GuildScheduledEventUserRemoveEventArgs.cs
@@ -1,58 +1,58 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildScheduledEventUserRemoved"/> event.
/// </summary>
public class GuildScheduledEventUserRemoveEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the scheduled event.
/// </summary>
public DiscordScheduledEvent ScheduledEvent { get; internal set; }
/// <summary>
/// Gets the guild.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the user which has unsubscribed from this scheduled event.
/// </summary>
public DiscordUser User { get; internal set; }
/// <summary>
/// Gets the member which has unsubscribed from this scheduled event.
/// </summary>
public DiscordMember Member { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildScheduledEventUserRemoveEventArgs"/> class.
/// </summary>
internal GuildScheduledEventUserRemoveEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/Thread/ThreadCreateEventArgs.cs b/DisCatSharp/EventArgs/Guild/Thread/ThreadCreateEventArgs.cs
index 0d5e36117..b6e9c0275 100644
--- a/DisCatSharp/EventArgs/Guild/Thread/ThreadCreateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/Thread/ThreadCreateEventArgs.cs
@@ -1,53 +1,53 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.ThreadCreated"/> event.
/// </summary>
public class ThreadCreateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the thread that was created.
/// </summary>
public DiscordThreadChannel Thread { get; internal set; }
/// <summary>
/// Gets the threads parent channel.
/// </summary>
public DiscordChannel Parent { get; internal set; }
/// <summary>
/// Gets the guild in which the thread was created.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="ThreadCreateEventArgs"/> class.
/// </summary>
internal ThreadCreateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/Thread/ThreadDeleteEventArgs.cs b/DisCatSharp/EventArgs/Guild/Thread/ThreadDeleteEventArgs.cs
index fb364627d..811a2066d 100644
--- a/DisCatSharp/EventArgs/Guild/Thread/ThreadDeleteEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/Thread/ThreadDeleteEventArgs.cs
@@ -1,59 +1,59 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
namespace DisCatSharp.EventArgs;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.ThreadDeleted"/> event.
/// </summary>
public class ThreadDeleteEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the thread that was deleted.
/// </summary>
public DiscordThreadChannel Thread { get; internal set; }
/// <summary>
/// Gets the threads parent channel.
/// </summary>
public DiscordChannel Parent { get; internal set; }
/// <summary>
/// Gets the guild this thread belonged to.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the threads type.
/// </summary>
public ChannelType Type { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="ThreadDeleteEventArgs"/> class.
/// </summary>
internal ThreadDeleteEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/Thread/ThreadListSyncEventArgs.cs b/DisCatSharp/EventArgs/Guild/Thread/ThreadListSyncEventArgs.cs
index 24865cd0a..1aeeb4088 100644
--- a/DisCatSharp/EventArgs/Guild/Thread/ThreadListSyncEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/Thread/ThreadListSyncEventArgs.cs
@@ -1,59 +1,59 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using DisCatSharp.Entities;
namespace DisCatSharp.EventArgs;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.ThreadListSynced"/> event.
/// </summary>
public class ThreadListSyncEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets all thread member objects from the synced threads for the current user, indicating which threads the current user has been added to
/// </summary>
public IReadOnlyList<DiscordThreadChannelMember> Members { get; internal set; }
/// <summary>
/// Gets all active threads in the given channels that the current user can access.
/// </summary>
public IReadOnlyList<DiscordThreadChannel> Threads { get; internal set; }
/// <summary>
/// Gets the parent channels whose threads are being synced. If empty, then threads are synced for the guild. May contain channels that have no active threads as well.
/// </summary>
public IReadOnlyList<DiscordChannel> Channels { get; internal set; }
/// <summary>
/// Gets the guild being synced.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="ThreadListSyncEventArgs"/> class.
/// </summary>
internal ThreadListSyncEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/Thread/ThreadMemberUpdateEventArgs.cs b/DisCatSharp/EventArgs/Guild/Thread/ThreadMemberUpdateEventArgs.cs
index 996f90444..79000595d 100644
--- a/DisCatSharp/EventArgs/Guild/Thread/ThreadMemberUpdateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/Thread/ThreadMemberUpdateEventArgs.cs
@@ -1,48 +1,48 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.ThreadMemberUpdated"/> event.
/// </summary>
public class ThreadMemberUpdateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the thread member that was updated.
/// </summary>
public DiscordThreadChannelMember ThreadMember { get; internal set; }
/// <summary>
/// Gets the thread.
/// </summary>
public DiscordThreadChannel Thread { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="ThreadMemberUpdateEventArgs"/> class.
/// </summary>
internal ThreadMemberUpdateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/Thread/ThreadMembersUpdateEventArgs.cs b/DisCatSharp/EventArgs/Guild/Thread/ThreadMembersUpdateEventArgs.cs
index 759f70bdf..a69e3d44d 100644
--- a/DisCatSharp/EventArgs/Guild/Thread/ThreadMembersUpdateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/Thread/ThreadMembersUpdateEventArgs.cs
@@ -1,70 +1,70 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using DisCatSharp.Entities;
namespace DisCatSharp.EventArgs;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.ThreadMembersUpdated"/> event.
/// </summary>
public class ThreadMembersUpdateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the approximate number of members in the thread, capped at 50.
/// </summary>
public int MemberCount { get; internal set; }
/// <summary>
/// Gets the users who were removed from the thread.
/// </summary>
public IReadOnlyList<DiscordMember> RemovedMembers { get; internal set; }
/// <summary>
/// Gets the users who were added to the thread.
/// </summary>
public IReadOnlyList<DiscordThreadChannelMember> AddedMembers { get; internal set; }
/// <summary>
/// Gets the id of the thread.
/// </summary>
public ulong ThreadId { get; internal set; }
/// <summary>
/// Gets the id of the thread.
/// </summary>
public DiscordThreadChannel Thread { get; internal set; }
/// <summary>
/// Gets the guild.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="ThreadMembersUpdateEventArgs"/> class.
/// </summary>
internal ThreadMembersUpdateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/Thread/ThreadUpdateEventArgs.cs b/DisCatSharp/EventArgs/Guild/Thread/ThreadUpdateEventArgs.cs
index 1e1c5f7b3..9fff1e89a 100644
--- a/DisCatSharp/EventArgs/Guild/Thread/ThreadUpdateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/Thread/ThreadUpdateEventArgs.cs
@@ -1,58 +1,58 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.ThreadUpdated"/> event.
/// </summary>
public class ThreadUpdateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the post-update thread.
/// </summary>
public DiscordThreadChannel ThreadAfter { get; internal set; }
/// <summary>
/// Gets the pre-update thread.
/// </summary>
public DiscordThreadChannel ThreadBefore { get; internal set; }
/// <summary>
/// Gets the threads parent channel.
/// </summary>
public DiscordChannel Parent { get; internal set; }
/// <summary>
/// Gets the guild in which the thread was updated.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="ThreadUpdateEventArgs"/> class.
/// </summary>
internal ThreadUpdateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/Timeout/GuildMemberTimeoutAddEventArgs.cs b/DisCatSharp/EventArgs/Guild/Timeout/GuildMemberTimeoutAddEventArgs.cs
index ecb359050..ed75ae174 100644
--- a/DisCatSharp/EventArgs/Guild/Timeout/GuildMemberTimeoutAddEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/Timeout/GuildMemberTimeoutAddEventArgs.cs
@@ -1,68 +1,68 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildMemberTimeoutAdded"/> event.
/// </summary>
public class GuildMemberTimeoutAddEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the member that was timed out.
/// </summary>
public DiscordMember Target { get; internal set; }
/// <summary>
/// Gets the member that timed out the member.
/// </summary>
public DiscordMember Actor { get; internal set; }
/// <summary>
/// Gets the guild this member was timed out.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the audit log id.
/// </summary>
public ulong? AuditLogId { get; internal set; }
/// <summary>
/// Gets the audit log reason.
/// </summary>
public string AuditLogReason { get; internal set; }
/// <summary>
/// Gets the timeout time.
/// </summary>
public DateTimeOffset Timeout { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildMemberTimeoutAddEventArgs"/> class.
/// </summary>
internal GuildMemberTimeoutAddEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/Timeout/GuildMemberTimeoutRemoveEventArgs.cs b/DisCatSharp/EventArgs/Guild/Timeout/GuildMemberTimeoutRemoveEventArgs.cs
index 508552be0..dc56ae683 100644
--- a/DisCatSharp/EventArgs/Guild/Timeout/GuildMemberTimeoutRemoveEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/Timeout/GuildMemberTimeoutRemoveEventArgs.cs
@@ -1,68 +1,68 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildMemberTimeoutRemoved"/> event.
/// </summary>
public class GuildMemberTimeoutRemoveEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the member that was affected by the timeout.
/// </summary>
public DiscordMember Target { get; internal set; }
/// <summary>
/// Gets the member that removed the timeout for the member.
/// </summary>
public DiscordMember Actor { get; internal set; }
/// <summary>
/// Gets the guild this member was timed out.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the audit log id.
/// </summary>
public ulong? AuditLogId { get; internal set; }
/// <summary>
/// Gets the audit log reason.
/// </summary>
public string AuditLogReason { get; internal set; }
/// <summary>
/// Gets the timeout time before the remove.
/// </summary>
public DateTimeOffset TimeoutBefore { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildMemberTimeoutRemoveEventArgs"/> class.
/// </summary>
internal GuildMemberTimeoutRemoveEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Guild/Timeout/GuildMemberTimeoutUpdateEventArgs.cs b/DisCatSharp/EventArgs/Guild/Timeout/GuildMemberTimeoutUpdateEventArgs.cs
index ea0c5f18c..50ebc9aba 100644
--- a/DisCatSharp/EventArgs/Guild/Timeout/GuildMemberTimeoutUpdateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Guild/Timeout/GuildMemberTimeoutUpdateEventArgs.cs
@@ -1,73 +1,73 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.GuildMemberTimeoutChanged"/> event.
/// </summary>
public class GuildMemberTimeoutUpdateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the member that was timed out.
/// </summary>
public DiscordMember Target { get; internal set; }
/// <summary>
/// Gets the member that timed out the member.
/// </summary>
public DiscordMember Actor { get; internal set; }
/// <summary>
/// Gets the guild this member was timed out.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the audit log id.
/// </summary>
public ulong? AuditLogId { get; internal set; }
/// <summary>
/// Gets the audit log reason.
/// </summary>
public string AuditLogReason { get; internal set; }
/// <summary>
/// Gets the timeout time before the update.
/// </summary>
public DateTimeOffset TimeoutBefore { get; internal set; }
/// <summary>
/// Gets the timeout time after the update.
/// </summary>
public DateTimeOffset TimeoutAfter { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildMemberTimeoutAddEventArgs"/> class.
/// </summary>
internal GuildMemberTimeoutUpdateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/HeartBeatEventArgs.cs b/DisCatSharp/EventArgs/HeartBeatEventArgs.cs
index 9a9e80b98..65a4f7dfa 100644
--- a/DisCatSharp/EventArgs/HeartBeatEventArgs.cs
+++ b/DisCatSharp/EventArgs/HeartBeatEventArgs.cs
@@ -1,46 +1,46 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.EventArgs;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.Heartbeated"/> event.
/// </summary>
public class HeartbeatEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the round-trip time of the heartbeat.
/// </summary>
public int Ping { get; internal set; }
/// <summary>
/// Gets the timestamp of the heartbeat.
/// </summary>
public DateTimeOffset Timestamp { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="HeartbeatEventArgs"/> class.
/// </summary>
internal HeartbeatEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Interaction/ContextMenuInteractionCreateEventArgs.cs b/DisCatSharp/EventArgs/Interaction/ContextMenuInteractionCreateEventArgs.cs
index 712b98732..4e3fdc821 100644
--- a/DisCatSharp/EventArgs/Interaction/ContextMenuInteractionCreateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Interaction/ContextMenuInteractionCreateEventArgs.cs
@@ -1,61 +1,61 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
namespace DisCatSharp.EventArgs;
/// <summary>
/// The context menu interaction create event args.
/// </summary>
public sealed class ContextMenuInteractionCreateEventArgs : InteractionCreateEventArgs
{
/// <summary>
/// The type of context menu that was used. This is never <see cref="DisCatSharp.Enums.ApplicationCommandType.ChatInput"/>.
/// </summary>
public ApplicationCommandType Type { get; internal set; }
/// <summary>
/// The user that invoked this interaction. Can be cast to a member if this was on a guild.
/// </summary>
public DiscordUser User => this.Interaction.User;
/// <summary>
/// The user this interaction targets, if applicable.
/// </summary>
public DiscordUser TargetUser { get; internal set; }
/// <summary>
/// The message this interaction targets, if applicable.
/// </summary>
public DiscordMessage TargetMessage { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="ContextMenuInteractionCreateEventArgs"/> class.
/// </summary>
/// <param name="provider">The provider.</param>
public ContextMenuInteractionCreateEventArgs(IServiceProvider provider) : base(provider)
{ }
}
diff --git a/DisCatSharp/EventArgs/Interaction/InteractionCreateEventArgs.cs b/DisCatSharp/EventArgs/Interaction/InteractionCreateEventArgs.cs
index 2d1284d00..a4801a551 100644
--- a/DisCatSharp/EventArgs/Interaction/InteractionCreateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Interaction/InteractionCreateEventArgs.cs
@@ -1,45 +1,45 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.InteractionCreated"/>
/// </summary>
public class InteractionCreateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the interaction data that was invoked.
/// </summary>
public DiscordInteraction Interaction { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="InteractionCreateEventArgs"/> class.
/// </summary>
/// <param name="provider">The provider.</param>
public InteractionCreateEventArgs(IServiceProvider provider) : base(provider)
{ }
}
diff --git a/DisCatSharp/EventArgs/Invite/InviteCreateEventArgs.cs b/DisCatSharp/EventArgs/Invite/InviteCreateEventArgs.cs
index ee5430d14..4cb103ea3 100644
--- a/DisCatSharp/EventArgs/Invite/InviteCreateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Invite/InviteCreateEventArgs.cs
@@ -1,53 +1,53 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.InviteCreated"/>
/// </summary>
public sealed class InviteCreateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the guild that created the invite.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the channel that the invite is for.
/// </summary>
public DiscordChannel Channel { get; internal set; }
/// <summary>
/// Gets the created invite.
/// </summary>
public DiscordInvite Invite { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="InviteCreateEventArgs"/> class.
/// </summary>
internal InviteCreateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Invite/InviteDeleteEventArgs.cs b/DisCatSharp/EventArgs/Invite/InviteDeleteEventArgs.cs
index 51a6b84b1..abb7ac820 100644
--- a/DisCatSharp/EventArgs/Invite/InviteDeleteEventArgs.cs
+++ b/DisCatSharp/EventArgs/Invite/InviteDeleteEventArgs.cs
@@ -1,53 +1,53 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.InviteDeleted"/>
/// </summary>
public sealed class InviteDeleteEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the guild that deleted the invite.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the channel that the invite was for.
/// </summary>
public DiscordChannel Channel { get; internal set; }
/// <summary>
/// Gets the deleted invite.
/// </summary>
public DiscordInvite Invite { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="InviteDeleteEventArgs"/> class.
/// </summary>
internal InviteDeleteEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Message/ComponentInteractionCreateEventArgs.cs b/DisCatSharp/EventArgs/Message/ComponentInteractionCreateEventArgs.cs
index 32909fb77..2e93f942e 100644
--- a/DisCatSharp/EventArgs/Message/ComponentInteractionCreateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Message/ComponentInteractionCreateEventArgs.cs
@@ -1,68 +1,68 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.ComponentInteractionCreated"/>.
/// </summary>
public class ComponentInteractionCreateEventArgs : InteractionCreateEventArgs
{
/// <summary>
/// The Id of the component that was interacted with.
/// </summary>
public string Id => this.Interaction.Data.CustomId;
/// <summary>
/// The user that invoked this interaction.
/// </summary>
public DiscordUser User => this.Interaction.User;
/// <summary>
/// The guild this interaction was invoked on, if any.
/// </summary>
public DiscordGuild Guild => this.Channel.Guild;
/// <summary>
/// The channel this interaction was invoked in.
/// </summary>
public DiscordChannel Channel => this.Interaction.Channel;
/// <summary>
/// The value(s) selected. Only applicable to SelectMenu components.
/// </summary>
public string[] Values => this.Interaction.Data.Values;
/// <summary>
/// The message this interaction is attached to.
/// </summary>
public DiscordMessage Message { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="ComponentInteractionCreateEventArgs"/> class.
/// </summary>
internal ComponentInteractionCreateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Message/MessageAcknowledgeEventArgs.cs b/DisCatSharp/EventArgs/Message/MessageAcknowledgeEventArgs.cs
index fdbd03eaf..0315d4b99 100644
--- a/DisCatSharp/EventArgs/Message/MessageAcknowledgeEventArgs.cs
+++ b/DisCatSharp/EventArgs/Message/MessageAcknowledgeEventArgs.cs
@@ -1,49 +1,49 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.MessageAcknowledged"/> event.
/// </summary>
public sealed class MessageAcknowledgeEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the message that was acknowledged.
/// </summary>
public DiscordMessage Message { get; internal set; }
/// <summary>
/// Gets the channel for which the message was acknowledged.
/// </summary>
public DiscordChannel Channel
=> this.Message.Channel;
/// <summary>
/// Initializes a new instance of the <see cref="MessageAcknowledgeEventArgs"/> class.
/// </summary>
internal MessageAcknowledgeEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Message/MessageBulkDeleteEventArgs.cs b/DisCatSharp/EventArgs/Message/MessageBulkDeleteEventArgs.cs
index c287f89aa..017ccfe50 100644
--- a/DisCatSharp/EventArgs/Message/MessageBulkDeleteEventArgs.cs
+++ b/DisCatSharp/EventArgs/Message/MessageBulkDeleteEventArgs.cs
@@ -1,54 +1,54 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using DisCatSharp.Entities;
namespace DisCatSharp.EventArgs;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.MessagesBulkDeleted"/> event.
/// </summary>
public class MessageBulkDeleteEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets a collection of the deleted messages.
/// </summary>
public IReadOnlyList<DiscordMessage> Messages { get; internal set; }
/// <summary>
/// Gets the channel in which the deletion occurred.
/// </summary>
public DiscordChannel Channel { get; internal set; }
/// <summary>
/// Gets the guild in which the deletion occurred.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="MessageBulkDeleteEventArgs"/> class.
/// </summary>
internal MessageBulkDeleteEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Message/MessageCreateEventArgs.cs b/DisCatSharp/EventArgs/Message/MessageCreateEventArgs.cs
index 07e5d5e91..44e5f76cc 100644
--- a/DisCatSharp/EventArgs/Message/MessageCreateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Message/MessageCreateEventArgs.cs
@@ -1,83 +1,83 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using DisCatSharp.Entities;
namespace DisCatSharp.EventArgs;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.MessageCreated"/> event.
/// </summary>
public class MessageCreateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the message that was created.
/// </summary>
public DiscordMessage Message { get; internal set; }
/// <summary>
/// Gets the channel this message belongs to.
/// </summary>
public DiscordChannel Channel
=> this.Message.Channel;
/// <summary>
/// Gets the guild this message belongs to.
/// </summary>
public DiscordGuild Guild
=> this.Message.Guild;
/// <summary>
/// Gets the guild id in case it couldn't convert.
/// </summary>
public ulong? GuildId
=> this.Message.GuildId;
/// <summary>
/// Gets the author of the message.
/// </summary>
public DiscordUser Author
=> this.Message.Author;
/// <summary>
/// Gets the collection of mentioned users.
/// </summary>
public IReadOnlyList<DiscordUser> MentionedUsers { get; internal set; }
/// <summary>
/// Gets the collection of mentioned roles.
/// </summary>
public IReadOnlyList<DiscordRole> MentionedRoles { get; internal set; }
/// <summary>
/// Gets the collection of mentioned channels.
/// </summary>
public IReadOnlyList<DiscordChannel> MentionedChannels { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="MessageCreateEventArgs"/> class.
/// </summary>
internal MessageCreateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Message/MessageDeleteEventArgs.cs b/DisCatSharp/EventArgs/Message/MessageDeleteEventArgs.cs
index b127eb601..3b1eb3bba 100644
--- a/DisCatSharp/EventArgs/Message/MessageDeleteEventArgs.cs
+++ b/DisCatSharp/EventArgs/Message/MessageDeleteEventArgs.cs
@@ -1,53 +1,53 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.MessageDeleted"/> event.
/// </summary>
public class MessageDeleteEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the message that was deleted.
/// </summary>
public DiscordMessage Message { get; internal set; }
/// <summary>
/// Gets the channel this message belonged to.
/// </summary>
public DiscordChannel Channel { get; internal set; }
/// <summary>
/// Gets the guild this message belonged to.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="MessageDeleteEventArgs"/> class.
/// </summary>
internal MessageDeleteEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Message/MessageUpdateEventArgs.cs b/DisCatSharp/EventArgs/Message/MessageUpdateEventArgs.cs
index 339397d25..90bc20590 100644
--- a/DisCatSharp/EventArgs/Message/MessageUpdateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Message/MessageUpdateEventArgs.cs
@@ -1,88 +1,88 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using DisCatSharp.Entities;
namespace DisCatSharp.EventArgs;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.MessageUpdated"/> event.
/// </summary>
public class MessageUpdateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the message that was updated.
/// </summary>
public DiscordMessage Message { get; internal set; }
/// <summary>
/// Gets the message before it got updated. This property will be null if the message was not cached.
/// </summary>
public DiscordMessage MessageBefore { get; internal set; }
/// <summary>
/// Gets the channel this message belongs to.
/// </summary>
public DiscordChannel Channel
=> this.Message.Channel;
/// <summary>
/// Gets the guild this message belongs to.
/// </summary>
public DiscordGuild Guild
=> this.Channel.Guild;
/// <summary>
/// Gets the guild id in case it couldn't convert.
/// </summary>
public ulong? GuildId
=> this.Message.GuildId;
/// <summary>
/// Gets the author of the message.
/// </summary>
public DiscordUser Author
=> this.Message.Author;
/// <summary>
/// Gets the collection of mentioned users.
/// </summary>
public IReadOnlyList<DiscordUser> MentionedUsers { get; internal set; }
/// <summary>
/// Gets the collection of mentioned roles.
/// </summary>
public IReadOnlyList<DiscordRole> MentionedRoles { get; internal set; }
/// <summary>
/// Gets the collection of mentioned channels.
/// </summary>
public IReadOnlyList<DiscordChannel> MentionedChannels { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="MessageUpdateEventArgs"/> class.
/// </summary>
internal MessageUpdateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Message/Reaction/MessageReactionAddEventArgs.cs b/DisCatSharp/EventArgs/Message/Reaction/MessageReactionAddEventArgs.cs
index 72e3bcfa5..edf8cf2d3 100644
--- a/DisCatSharp/EventArgs/Message/Reaction/MessageReactionAddEventArgs.cs
+++ b/DisCatSharp/EventArgs/Message/Reaction/MessageReactionAddEventArgs.cs
@@ -1,78 +1,78 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.MessageReactionAdded"/> event.
/// </summary>
public class MessageReactionAddEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the message for which the update occurred.
/// </summary>
public DiscordMessage Message { get; internal set; }
/// <summary>
/// Gets the channel to which this message belongs.
/// </summary>
/// <remarks>
/// This will be <c>null</c> for an uncached channel, which will usually happen for when this event triggers on
/// DM channels in which no prior messages were received or sent.
/// </remarks>
public DiscordChannel Channel { get; internal set; }
/// <summary>
/// The channel id.
/// </summary>s
public ulong ChannelId { get; internal set; }
/// <summary>
/// Gets the user who created the reaction.
/// <para>This can be cast to a <see cref="DisCatSharp.Entities.DiscordMember"/> if the reaction was in a guild.</para>
/// </summary>
public DiscordUser User { get; internal set; }
/// <summary>
/// Gets the guild in which the reaction was added.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the emoji used for this reaction.
/// </summary>
public DiscordEmoji Emoji { get; internal set; }
/// <summary>
/// Whether the reaction was added as a burst reaction.
/// </summary>
public bool IsBurst { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="MessageReactionAddEventArgs"/> class.
/// </summary>
internal MessageReactionAddEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Message/Reaction/MessageReactionRemoveEmojiEventArgs.cs b/DisCatSharp/EventArgs/Message/Reaction/MessageReactionRemoveEmojiEventArgs.cs
index 1cb2d61ff..f2f875f86 100644
--- a/DisCatSharp/EventArgs/Message/Reaction/MessageReactionRemoveEmojiEventArgs.cs
+++ b/DisCatSharp/EventArgs/Message/Reaction/MessageReactionRemoveEmojiEventArgs.cs
@@ -1,58 +1,58 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.MessageReactionRemovedEmoji"/>
/// </summary>
public sealed class MessageReactionRemoveEmojiEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the channel the removed reactions were in.
/// </summary>
public DiscordChannel Channel { get; internal set; }
/// <summary>
/// Gets the guild the removed reactions were in.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the message that had the removed reactions.
/// </summary>
public DiscordMessage Message { get; internal set; }
/// <summary>
/// Gets the emoji of the reaction that was removed.
/// </summary>
public DiscordEmoji Emoji { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="MessageReactionRemoveEmojiEventArgs"/> class.
/// </summary>
internal MessageReactionRemoveEmojiEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Message/Reaction/MessageReactionRemoveEventArgs.cs b/DisCatSharp/EventArgs/Message/Reaction/MessageReactionRemoveEventArgs.cs
index b15e618b1..ba8dc29e2 100644
--- a/DisCatSharp/EventArgs/Message/Reaction/MessageReactionRemoveEventArgs.cs
+++ b/DisCatSharp/EventArgs/Message/Reaction/MessageReactionRemoveEventArgs.cs
@@ -1,77 +1,77 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.MessageReactionRemoved"/> event.
/// </summary>
public class MessageReactionRemoveEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the message for which the update occurred.
/// </summary>
public DiscordMessage Message { get; internal set; }
/// <summary>
/// Gets the channel to which this message belongs.
/// </summary>
/// <remarks>
/// This will be <c>null</c> for an uncached channel, which will usually happen for when this event triggers on
/// DM channels in which no prior messages were received or sent.
/// </remarks>
public DiscordChannel Channel { get; internal set; }
/// <summary>
/// The channel id.
/// </summary>s
public ulong ChannelId { get; internal set; }
/// <summary>
/// Gets the users whose reaction was removed.
/// </summary>
public DiscordUser User { get; internal set; }
/// <summary>
/// Gets the guild in which the reaction was deleted.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the emoji used for this reaction.
/// </summary>
public DiscordEmoji Emoji { get; internal set; }
/// <summary>
/// Whether the reaction was added as a burst reaction.
/// </summary>
public bool IsBurst { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="MessageReactionRemoveEventArgs"/> class.
/// </summary>
internal MessageReactionRemoveEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Message/Reaction/MessageReactionsClearEventArgs.cs b/DisCatSharp/EventArgs/Message/Reaction/MessageReactionsClearEventArgs.cs
index e7cf0bf35..47dac9920 100644
--- a/DisCatSharp/EventArgs/Message/Reaction/MessageReactionsClearEventArgs.cs
+++ b/DisCatSharp/EventArgs/Message/Reaction/MessageReactionsClearEventArgs.cs
@@ -1,59 +1,59 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.MessageReactionsCleared"/> event.
/// </summary>
public class MessageReactionsClearEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the message for which the update occurred.
/// </summary>
public DiscordMessage Message { get; internal set; }
/// <summary>
/// Gets the channel to which this message belongs.
/// </summary>
/// <remarks>
/// This will be <c>null</c> for an uncached channel, which will usually happen for when this event triggers on
/// DM channels in which no prior messages were received or sent.
/// </remarks>
public DiscordChannel Channel
=> this.Message.Channel;
/// <summary>
/// Gets the guild in which the reactions were cleared.
/// </summary>
public DiscordGuild Guild
=> this.Channel.Guild;
/// <summary>
/// Initializes a new instance of the <see cref="MessageReactionsClearEventArgs"/> class.
/// </summary>
internal MessageReactionsClearEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/PayloadReceivedEventArgs.cs b/DisCatSharp/EventArgs/PayloadReceivedEventArgs.cs
index c60a90ac4..631b0f339 100644
--- a/DisCatSharp/EventArgs/PayloadReceivedEventArgs.cs
+++ b/DisCatSharp/EventArgs/PayloadReceivedEventArgs.cs
@@ -1,62 +1,62 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Linq;
namespace DisCatSharp.EventArgs;
/// <summary>
/// Represents a gateway payload.
/// </summary>
public class PayloadReceivedEventArgs : DiscordEventArgs
{
/// <summary>
/// The JSON from this payload event.
/// </summary>
public string Json
{
get
{
if (string.IsNullOrWhiteSpace(this._json))
this._json = this.PayloadObject.ToString();
return this._json;
}
}
private string _json;
/// <summary>
/// Gets or sets the payload object.
/// </summary>
internal JObject PayloadObject { get; set; }
/// <summary>
/// The name of this event.
/// </summary>
public string EventName { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="PayloadReceivedEventArgs"/> class.
/// </summary>
internal PayloadReceivedEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/RateLimitExceptionEventArgs.cs b/DisCatSharp/EventArgs/RateLimitExceptionEventArgs.cs
index 3c0f32720..73e341f80 100644
--- a/DisCatSharp/EventArgs/RateLimitExceptionEventArgs.cs
+++ b/DisCatSharp/EventArgs/RateLimitExceptionEventArgs.cs
@@ -1,42 +1,42 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Exceptions;
namespace DisCatSharp.EventArgs;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.RateLimitHit"/> event.
/// </summary>
public class RateLimitExceptionEventArgs : DiscordEventArgs
{
public RateLimitException Exception { get; internal set; }
public string ApiEndpoint { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="HeartbeatEventArgs"/> class.
/// </summary>
internal RateLimitExceptionEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/ReadyEventArgs.cs b/DisCatSharp/EventArgs/ReadyEventArgs.cs
index 3f6127fda..f3387a5e5 100644
--- a/DisCatSharp/EventArgs/ReadyEventArgs.cs
+++ b/DisCatSharp/EventArgs/ReadyEventArgs.cs
@@ -1,36 +1,36 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.EventArgs;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.Ready"/> event.
/// </summary>
public sealed class ReadyEventArgs : DiscordEventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="ReadyEventArgs"/> class.
/// </summary>
internal ReadyEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Socket/SocketDisconnectEventArgs.cs b/DisCatSharp/EventArgs/Socket/SocketDisconnectEventArgs.cs
index 4ff6353b5..d8addd5b5 100644
--- a/DisCatSharp/EventArgs/Socket/SocketDisconnectEventArgs.cs
+++ b/DisCatSharp/EventArgs/Socket/SocketDisconnectEventArgs.cs
@@ -1,62 +1,62 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.EventArgs;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.SocketClosed"/> event.
/// </summary>
public class SocketCloseEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the close code sent by remote host.
/// </summary>
public int CloseCode { get; internal set; }
/// <summary>
/// Gets the close message sent by remote host.
/// </summary>
public string CloseMessage { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="SocketCloseEventArgs"/> class.
/// </summary>
public SocketCloseEventArgs(IServiceProvider provider) : base(provider) { }
}
/// <summary>
/// Represents arguments for <see cref="DiscordClient.SocketErrored"/> event.
/// </summary>
public class SocketErrorEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the exception thrown by websocket client.
/// </summary>
public Exception Exception { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="SocketErrorEventArgs"/> class.
/// </summary>
public SocketErrorEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Socket/SocketEventArgs.cs b/DisCatSharp/EventArgs/Socket/SocketEventArgs.cs
index e618f4e4d..2f776786e 100644
--- a/DisCatSharp/EventArgs/Socket/SocketEventArgs.cs
+++ b/DisCatSharp/EventArgs/Socket/SocketEventArgs.cs
@@ -1,36 +1,36 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.EventArgs;
/// <summary>
/// Represents basic socket event arguments.
/// </summary>
public class SocketEventArgs : DiscordEventArgs
{
/// <summary>
/// Creates a new event argument container.
/// </summary>
public SocketEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Socket/WebSocketMessageEventArgs.cs b/DisCatSharp/EventArgs/Socket/WebSocketMessageEventArgs.cs
index db5eaa561..2e7b7057e 100644
--- a/DisCatSharp/EventArgs/Socket/WebSocketMessageEventArgs.cs
+++ b/DisCatSharp/EventArgs/Socket/WebSocketMessageEventArgs.cs
@@ -1,71 +1,71 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Common.Utilities;
namespace DisCatSharp.EventArgs;
/// <summary>
/// Represents base class for raw socket message event arguments.
/// </summary>
public abstract class SocketMessageEventArgs : AsyncEventArgs
{ }
/// <summary>
/// Represents arguments for text message websocket event.
/// </summary>
public sealed class SocketTextMessageEventArgs : SocketMessageEventArgs
{
/// <summary>
/// Gets the received message string.
/// </summary>
public string Message { get; }
/// <summary>
/// Creates a new instance of text message event arguments.
/// </summary>
/// <param name="message">Received message string.</param>
public SocketTextMessageEventArgs(string message)
{
this.Message = message;
}
}
/// <summary>
/// Represents arguments for binary message websocket event.
/// </summary>
public sealed class SocketBinaryMessageEventArgs : SocketMessageEventArgs
{
/// <summary>
/// Gets the received message bytes.
/// </summary>
public byte[] Message { get; }
/// <summary>
/// Creates a new instance of binary message event arguments.
/// </summary>
/// <param name="message">Received message bytes.</param>
public SocketBinaryMessageEventArgs(byte[] message)
{
this.Message = message;
}
}
diff --git a/DisCatSharp/EventArgs/Stage/StageInstanceCreateEventArgs.cs b/DisCatSharp/EventArgs/Stage/StageInstanceCreateEventArgs.cs
index 250657f36..255777a5d 100644
--- a/DisCatSharp/EventArgs/Stage/StageInstanceCreateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Stage/StageInstanceCreateEventArgs.cs
@@ -1,48 +1,48 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.StageInstanceCreated"/> event.
/// </summary>
public class StageInstanceCreateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the stage instance that was created.
/// </summary>
public DiscordStageInstance StageInstance { get; internal set; }
/// <summary>
/// Gets the guild in which the stage instance was created.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="StageInstanceCreateEventArgs"/> class.
/// </summary>
internal StageInstanceCreateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Stage/StageInstanceDeleteEventArgs.cs b/DisCatSharp/EventArgs/Stage/StageInstanceDeleteEventArgs.cs
index 0a43d4d5e..6d0432eba 100644
--- a/DisCatSharp/EventArgs/Stage/StageInstanceDeleteEventArgs.cs
+++ b/DisCatSharp/EventArgs/Stage/StageInstanceDeleteEventArgs.cs
@@ -1,48 +1,48 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.StageInstanceDeleted"/> event.
/// </summary>
public class StageInstanceDeleteEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the stage instance that was deleted.
/// </summary>
public DiscordStageInstance StageInstance { get; internal set; }
/// <summary>
/// Gets the guild in which the stage instance was deleted.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="StageInstanceDeleteEventArgs"/> class.
/// </summary>
internal StageInstanceDeleteEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Stage/StageInstanceUpdateEventArgs.cs b/DisCatSharp/EventArgs/Stage/StageInstanceUpdateEventArgs.cs
index 4cad1b26d..6fed0665c 100644
--- a/DisCatSharp/EventArgs/Stage/StageInstanceUpdateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Stage/StageInstanceUpdateEventArgs.cs
@@ -1,48 +1,48 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.StageInstanceUpdated"/> event.
/// </summary>
public class StageInstanceUpdateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the stage instance that was updated.
/// </summary>
public DiscordStageInstance StageInstance { get; internal set; }
/// <summary>
/// Gets the guild in which the stage instance was updated.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="StageInstanceUpdateEventArgs"/> class.
/// </summary>
internal StageInstanceUpdateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/TypingStartEventArgs.cs b/DisCatSharp/EventArgs/TypingStartEventArgs.cs
index bfae02e72..7623cfa33 100644
--- a/DisCatSharp/EventArgs/TypingStartEventArgs.cs
+++ b/DisCatSharp/EventArgs/TypingStartEventArgs.cs
@@ -1,59 +1,59 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.TypingStarted"/> event.
/// </summary>
public class TypingStartEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the channel in which the indicator was triggered.
/// </summary>
public DiscordChannel Channel { get; internal set; }
/// <summary>
/// Gets the user that started typing.
/// <para>This can be cast to a <see cref="DisCatSharp.Entities.DiscordMember"/> if the typing occurred in a guild.</para>
/// </summary>
public DiscordUser User { get; internal set; }
/// <summary>
/// Gets the guild in which the indicator was triggered.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the date and time at which the user started typing.
/// </summary>
public DateTimeOffset StartedAt { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="TypingStartEventArgs"/> class.
/// </summary>
internal TypingStartEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/UnknownEventArgs.cs b/DisCatSharp/EventArgs/UnknownEventArgs.cs
index 6d34a5207..eb8d73e21 100644
--- a/DisCatSharp/EventArgs/UnknownEventArgs.cs
+++ b/DisCatSharp/EventArgs/UnknownEventArgs.cs
@@ -1,48 +1,48 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.EventArgs;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.UnknownEvent"/> event.
/// </summary>
public class UnknownEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the event's name.
/// </summary>
public string EventName { get; internal set; }
/// <summary>
/// Gets the event's data.
/// </summary>
public string Json { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="UnknownEventArgs"/> class.
/// </summary>
internal UnknownEventArgs(IServiceProvider provider)
: base(provider)
{ }
}
diff --git a/DisCatSharp/EventArgs/User/PresenceUpdateEventArgs.cs b/DisCatSharp/EventArgs/User/PresenceUpdateEventArgs.cs
index 5c295710d..add736b5a 100644
--- a/DisCatSharp/EventArgs/User/PresenceUpdateEventArgs.cs
+++ b/DisCatSharp/EventArgs/User/PresenceUpdateEventArgs.cs
@@ -1,63 +1,63 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.PresenceUpdated"/> event.
/// </summary>
public class PresenceUpdateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the user whose presence was updated.
/// </summary>
public DiscordUser User { get; internal set; }
/// <summary>
/// Gets the user's new game.
/// </summary>
public DiscordActivity Activity { get; internal set; }
/// <summary>
/// Gets the user's status.
/// </summary>
public UserStatus Status { get; internal set; }
/// <summary>
/// Gets the user's old presence.
/// </summary>
public DiscordPresence PresenceBefore { get; internal set; }
/// <summary>
/// Gets the user's new presence.
/// </summary>
public DiscordPresence PresenceAfter { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="PresenceUpdateEventArgs"/> class.
/// </summary>
internal PresenceUpdateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/User/UserSettingsUpdateEventArgs.cs b/DisCatSharp/EventArgs/User/UserSettingsUpdateEventArgs.cs
index a0c9cce57..3bca5a2f6 100644
--- a/DisCatSharp/EventArgs/User/UserSettingsUpdateEventArgs.cs
+++ b/DisCatSharp/EventArgs/User/UserSettingsUpdateEventArgs.cs
@@ -1,43 +1,43 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.UserSettingsUpdated"/> event.
/// </summary>
public class UserSettingsUpdateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the user whose settings were updated.
/// </summary>
public DiscordUser User { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="UserSettingsUpdateEventArgs"/> class.
/// </summary>
internal UserSettingsUpdateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/User/UserSpeakingEventArgs.cs b/DisCatSharp/EventArgs/User/UserSpeakingEventArgs.cs
index 268b3b087..c6f9cbccf 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-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for UserSpeaking event.
/// </summary>
public class UserSpeakingEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the users whose speaking state changed.
/// </summary>
public DiscordUser User { get; internal set; }
/// <summary>
/// Gets the SSRC of the audio source.
/// </summary>
public uint Ssrc { get; internal set; }
/// <summary>
/// Gets whether this user is speaking.
/// </summary>
public bool Speaking { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="UserSpeakingEventArgs"/> class.
/// </summary>
internal UserSpeakingEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/User/UserUpdateEventArgs.cs b/DisCatSharp/EventArgs/User/UserUpdateEventArgs.cs
index edacca312..352faa781 100644
--- a/DisCatSharp/EventArgs/User/UserUpdateEventArgs.cs
+++ b/DisCatSharp/EventArgs/User/UserUpdateEventArgs.cs
@@ -1,48 +1,48 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.UserUpdated"/> event.
/// </summary>
public class UserUpdateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the post-update user.
/// </summary>
public DiscordUser UserAfter { get; internal set; }
/// <summary>
/// Gets the pre-update user.
/// </summary>
public DiscordUser UserBefore { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="UserUpdateEventArgs"/> class.
/// </summary>
internal UserUpdateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Voice/VoiceServerUpdateEventArgs.cs b/DisCatSharp/EventArgs/Voice/VoiceServerUpdateEventArgs.cs
index 20811c42a..e13ecc3bf 100644
--- a/DisCatSharp/EventArgs/Voice/VoiceServerUpdateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Voice/VoiceServerUpdateEventArgs.cs
@@ -1,53 +1,53 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.VoiceServerUpdated"/> event.
/// </summary>
public class VoiceServerUpdateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the guild for which the update occurred.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the new voice endpoint.
/// </summary>
public string Endpoint { get; internal set; }
/// <summary>
/// Gets the voice connection token.
/// </summary>
internal string VoiceToken { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="VoiceServerUpdateEventArgs"/> class.
/// </summary>
internal VoiceServerUpdateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/Voice/VoiceStateUpdateEventArgs.cs b/DisCatSharp/EventArgs/Voice/VoiceStateUpdateEventArgs.cs
index 9b924355e..cc28a4fbd 100644
--- a/DisCatSharp/EventArgs/Voice/VoiceStateUpdateEventArgs.cs
+++ b/DisCatSharp/EventArgs/Voice/VoiceStateUpdateEventArgs.cs
@@ -1,68 +1,68 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.VoiceStateUpdated"/> event.
/// </summary>
public class VoiceStateUpdateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the user whose voice state was updated.
/// </summary>
public DiscordUser User { get; internal set; }
/// <summary>
/// Gets the guild in which the update occurred.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the related voice channel.
/// </summary>
public DiscordChannel Channel { get; internal set; }
/// <summary>
/// Gets the voice state pre-update.
/// </summary>
public DiscordVoiceState Before { get; internal set; }
/// <summary>
/// Gets the voice state post-update.
/// </summary>
public DiscordVoiceState After { get; internal set; }
/// <summary>
/// Gets the ID of voice session.
/// </summary>
internal string SessionId { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="VoiceStateUpdateEventArgs"/> class.
/// </summary>
internal VoiceStateUpdateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/WebhooksUpdateEventArgs.cs b/DisCatSharp/EventArgs/WebhooksUpdateEventArgs.cs
index e679c78c4..9c98d2c56 100644
--- a/DisCatSharp/EventArgs/WebhooksUpdateEventArgs.cs
+++ b/DisCatSharp/EventArgs/WebhooksUpdateEventArgs.cs
@@ -1,48 +1,48 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents arguments to <see cref="DiscordClient.WebhooksUpdated"/> event.
/// </summary>
public class WebhooksUpdateEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets the guild that had its webhooks updated.
/// </summary>
public DiscordGuild Guild { get; internal set; }
/// <summary>
/// Gets the channel to which the webhook belongs to.
/// </summary>
public DiscordChannel Channel { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="WebhooksUpdateEventArgs"/> class.
/// </summary>
internal WebhooksUpdateEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/EventArgs/ZombiedEventArgs.cs b/DisCatSharp/EventArgs/ZombiedEventArgs.cs
index 9433652df..86fbbe2b3 100644
--- a/DisCatSharp/EventArgs/ZombiedEventArgs.cs
+++ b/DisCatSharp/EventArgs/ZombiedEventArgs.cs
@@ -1,46 +1,46 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.EventArgs;
/// <summary>
/// Represents arguments for <see cref="DiscordClient.Zombied"/> event.
/// </summary>
public class ZombiedEventArgs : DiscordEventArgs
{
/// <summary>
/// Gets how many heartbeat failures have occurred.
/// </summary>
public int Failures { get; internal set; }
/// <summary>
/// Gets whether the zombie event occurred whilst guilds are downloading.
/// </summary>
public bool GuildDownloadCompleted { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="ZombiedEventArgs"/> class.
/// </summary>
internal ZombiedEventArgs(IServiceProvider provider) : base(provider) { }
}
diff --git a/DisCatSharp/Exceptions/BadRequestException.cs b/DisCatSharp/Exceptions/BadRequestException.cs
index b2420d5de..d5463c340 100644
--- a/DisCatSharp/Exceptions/BadRequestException.cs
+++ b/DisCatSharp/Exceptions/BadRequestException.cs
@@ -1,86 +1,86 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net;
using Newtonsoft.Json.Linq;
namespace DisCatSharp.Exceptions;
/// <summary>
/// Represents an exception thrown when a malformed request is sent.
/// </summary>
public class BadRequestException : Exception
{
/// <summary>
/// Gets the request that caused the exception.
/// </summary>
public BaseRestRequest WebRequest { get; internal set; }
/// <summary>
/// Gets the response to the request.
/// </summary>
public RestResponse WebResponse { get; internal set; }
/// <summary>
/// Gets the error code for this exception.
/// </summary>
public int Code { get; internal set; }
/// <summary>
/// Gets the JSON message received.
/// </summary>
public string JsonMessage { get; internal set; }
/// <summary>
/// Gets the form error responses in JSON format.
/// </summary>
public string Errors { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="BadRequestException"/> class.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="response">The response.</param>
internal BadRequestException(BaseRestRequest request, RestResponse response) : base("Bad request: " + response.ResponseCode)
{
this.WebRequest = request;
this.WebResponse = response;
try
{
var j = JObject.Parse(response.Response);
if (j["code"] != null)
this.Code = (int)j["code"];
if (j["message"] != null)
this.JsonMessage = j["message"].ToString();
if (j["errors"] != null)
this.Errors = j["errors"].ToString();
}
catch { }
}
}
diff --git a/DisCatSharp/Exceptions/NotFoundException.cs b/DisCatSharp/Exceptions/NotFoundException.cs
index 55232657b..1b48f0d30 100644
--- a/DisCatSharp/Exceptions/NotFoundException.cs
+++ b/DisCatSharp/Exceptions/NotFoundException.cs
@@ -1,70 +1,70 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net;
using Newtonsoft.Json.Linq;
namespace DisCatSharp.Exceptions;
/// <summary>
/// Represents an exception thrown when a requested resource is not found.
/// </summary>
public class NotFoundException : Exception
{
/// <summary>
/// Gets the request that caused the exception.
/// </summary>
public BaseRestRequest WebRequest { get; internal set; }
/// <summary>
/// Gets the response to the request.
/// </summary>
public RestResponse WebResponse { get; internal set; }
/// <summary>
/// Gets the JSON received.
/// </summary>
public string JsonMessage { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="NotFoundException"/> class.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="response">The response.</param>
internal NotFoundException(BaseRestRequest request, RestResponse response) : base("Not found: " + response.ResponseCode)
{
this.WebRequest = request;
this.WebResponse = response;
try
{
var j = JObject.Parse(response.Response);
if (j["message"] != null)
this.JsonMessage = j["message"].ToString();
}
catch (Exception) { }
}
}
diff --git a/DisCatSharp/Exceptions/RateLimitException.cs b/DisCatSharp/Exceptions/RateLimitException.cs
index 568d8c907..6d9c8b590 100644
--- a/DisCatSharp/Exceptions/RateLimitException.cs
+++ b/DisCatSharp/Exceptions/RateLimitException.cs
@@ -1,70 +1,70 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net;
using Newtonsoft.Json.Linq;
namespace DisCatSharp.Exceptions;
/// <summary>
/// Represents an exception thrown when too many requests are sent.
/// </summary>
public class RateLimitException : Exception
{
/// <summary>
/// Gets the request that caused the exception.
/// </summary>
public BaseRestRequest WebRequest { get; internal set; }
/// <summary>
/// Gets the response to the request.
/// </summary>
public RestResponse WebResponse { get; internal set; }
/// <summary>
/// Gets the JSON received.
/// </summary>
public string JsonMessage { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="RateLimitException"/> class.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="response">The response.</param>
internal RateLimitException(BaseRestRequest request, RestResponse response) : base("Rate limited: " + response.ResponseCode)
{
this.WebRequest = request;
this.WebResponse = response;
try
{
var j = JObject.Parse(response.Response);
if (j["message"] != null)
this.JsonMessage = j["message"].ToString();
}
catch (Exception) { }
}
}
diff --git a/DisCatSharp/Exceptions/RequestSizeException.cs b/DisCatSharp/Exceptions/RequestSizeException.cs
index bdc3446bb..71205e0a9 100644
--- a/DisCatSharp/Exceptions/RequestSizeException.cs
+++ b/DisCatSharp/Exceptions/RequestSizeException.cs
@@ -1,70 +1,70 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net;
using Newtonsoft.Json.Linq;
namespace DisCatSharp.Exceptions;
/// <summary>
/// Represents an exception thrown when the request sent to Discord is too large.
/// </summary>
public class RequestSizeException : Exception
{
/// <summary>
/// Gets the request that caused the exception.
/// </summary>
public BaseRestRequest WebRequest { get; internal set; }
/// <summary>
/// Gets the response to the request.
/// </summary>
public RestResponse WebResponse { get; internal set; }
/// <summary>
/// Gets the JSON received.
/// </summary>
public string JsonMessage { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="RequestSizeException"/> class.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="response">The response.</param>
internal RequestSizeException(BaseRestRequest request, RestResponse response) : base($"Request entity too large: {response.ResponseCode}. Make sure the data sent is within Discord's upload limit.")
{
this.WebRequest = request;
this.WebResponse = response;
try
{
var j = JObject.Parse(response.Response);
if (j["message"] != null)
this.JsonMessage = j["message"].ToString();
}
catch (Exception) { }
}
}
diff --git a/DisCatSharp/Exceptions/ServerErrorException.cs b/DisCatSharp/Exceptions/ServerErrorException.cs
index f9cf0be55..863f1caed 100644
--- a/DisCatSharp/Exceptions/ServerErrorException.cs
+++ b/DisCatSharp/Exceptions/ServerErrorException.cs
@@ -1,70 +1,70 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net;
using Newtonsoft.Json.Linq;
namespace DisCatSharp.Exceptions;
/// <summary>
/// Represents an exception thrown when Discord returns an Internal Server Error.
/// </summary>
public class ServerErrorException : Exception
{
/// <summary>
/// Gets the request that caused the exception.
/// </summary>
public BaseRestRequest WebRequest { get; internal set; }
/// <summary>
/// Gets the response to the request.
/// </summary>
public RestResponse WebResponse { get; internal set; }
/// <summary>
/// Gets the JSON received.
/// </summary>
public string JsonMessage { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="ServerErrorException"/> class.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="response">The response.</param>
internal ServerErrorException(BaseRestRequest request, RestResponse response) : base("Internal Server Error: " + response.ResponseCode)
{
this.WebRequest = request;
this.WebResponse = response;
try
{
var j = JObject.Parse(response.Response);
if (j["message"] != null)
this.JsonMessage = j["message"].ToString();
}
catch (Exception) { }
}
}
diff --git a/DisCatSharp/Exceptions/UnauthorizedException.cs b/DisCatSharp/Exceptions/UnauthorizedException.cs
index 95bc91608..f25938ea5 100644
--- a/DisCatSharp/Exceptions/UnauthorizedException.cs
+++ b/DisCatSharp/Exceptions/UnauthorizedException.cs
@@ -1,70 +1,70 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net;
using Newtonsoft.Json.Linq;
namespace DisCatSharp.Exceptions;
/// <summary>
/// Represents an exception thrown when requester doesn't have necessary permissions to complete the request.
/// </summary>
public class UnauthorizedException : Exception
{
/// <summary>
/// Gets the request that caused the exception.
/// </summary>
public BaseRestRequest WebRequest { get; internal set; }
/// <summary>
/// Gets the response to the request.
/// </summary>
public RestResponse WebResponse { get; internal set; }
/// <summary>
/// Gets the JSON received.
/// </summary>
public string JsonMessage { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="UnauthorizedException"/> class.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="response">The response.</param>
internal UnauthorizedException(BaseRestRequest request, RestResponse response) : base("Unauthorized: " + response.ResponseCode)
{
this.WebRequest = request;
this.WebResponse = response;
try
{
var j = JObject.Parse(response.Response);
if (j["message"] != null)
this.JsonMessage = j["message"].ToString();
}
catch (Exception) { }
}
}
diff --git a/DisCatSharp/Formatter.cs b/DisCatSharp/Formatter.cs
index 395cb82c1..e4190c368 100644
--- a/DisCatSharp/Formatter.cs
+++ b/DisCatSharp/Formatter.cs
@@ -1,206 +1,206 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
using DisCatSharp.Enums;
namespace DisCatSharp;
/// <summary>
/// Contains markdown formatting helpers.
/// </summary>
public static class Formatter
{
/// <summary>
/// Gets the md sanitize regex.
/// </summary>
private static Regex s_mdSanitizeRegex { get; } = new(@"([`\*_~<>\[\]\(\)""@\!\&#:\|])", RegexOptions.ECMAScript);
/// <summary>
/// Gets the md strip regex.
/// </summary>
private static Regex s_mdStripRegex { get; } = new(@"([`\*_~\[\]\(\)""\|]|<@\!?\d+>|<#\d+>|<@\&\d+>|<:[a-zA-Z0-9_\-]:\d+>)", RegexOptions.ECMAScript);
/// <summary>
/// Creates a block of code.
/// </summary>
/// <param name="content">Contents of the block.</param>
/// <param name="language">Language to use for highlighting.</param>
/// <returns>Formatted block of code.</returns>
public static string BlockCode(this string content, string language = "")
=> $"```{language}\n{content}\n```";
/// <summary>
/// Creates inline code snippet.
/// </summary>
/// <param name="content">Contents of the snippet.</param>
/// <returns>Formatted inline code snippet.</returns>
public static string InlineCode(this string content)
=> $"`{content}`";
/// <summary>
/// Creates a rendered timestamp.
/// </summary>
/// <param name="time">The time from now.</param>
/// <param name="format">The format to render the timestamp in. Defaults to relative.</param>
/// <returns>A formatted timestamp.</returns>
public static string Timestamp(this TimeSpan time, TimestampFormat format = TimestampFormat.RelativeTime)
=> Timestamp(DateTimeOffset.UtcNow + time, format);
/// <summary>
/// Creates a rendered timestamp.
/// </summary>
/// <param name="time">Timestamp to format.</param>
/// <param name="format">The format to render the timestamp in. Defaults to relative.</param>
/// <returns>A formatted timestamp.</returns>
public static string Timestamp(this DateTimeOffset time, TimestampFormat format = TimestampFormat.RelativeTime)
=> $"<t:{time.ToUnixTimeSeconds()}:{(char)format}>";
/// <summary>
/// Creates a rendered timestamp.
/// </summary>
/// <param name="time">The time from now.</param>
/// <param name="format">The format to render the timestamp in. Defaults to relative.</param>
/// <returns>A formatted timestamp relative to now.</returns>
public static string Timestamp(this DateTime time, TimestampFormat format = TimestampFormat.RelativeTime)
=> Timestamp(time.ToUniversalTime() - DateTime.UtcNow, format);
/// <summary>
/// Creates bold text.
/// </summary>
/// <param name="content">Text to embolden.</param>
/// <returns>Formatted text.</returns>
public static string Bold(this string content)
=> $"**{content}**";
/// <summary>
/// Creates italicized text.
/// </summary>
/// <param name="content">Text to italicize.</param>
/// <returns>Formatted text.</returns>
public static string Italic(this string content)
=> $"*{content}*";
/// <summary>
/// Creates spoiler from text.
/// </summary>
/// <param name="content">Text to spoiler.</param>
/// <returns>Formatted text.</returns>
public static string Spoiler(this string content)
=> $"||{content}||";
/// <summary>
/// Creates underlined text.
/// </summary>
/// <param name="content">Text to underline.</param>
/// <returns>Formatted text.</returns>
public static string Underline(this string content)
=> $"__{content}__";
/// <summary>
/// Creates strikethrough text.
/// </summary>
/// <param name="content">Text to strikethrough.</param>
/// <returns>Formatted text.</returns>
public static string Strike(this string content)
=> $"~~{content}~~";
/// <summary>
/// Creates a URL that won't create a link preview.
/// </summary>
/// <param name="url">Url to prevent from being previewed.</param>
/// <returns>Formatted url.</returns>
public static string EmbedlessUrl(this Uri url)
=> $"<{url}>";
/// <summary>
/// Creates a masked link. This link will display as specified text, and alternatively provided alt text. This can only be used in embeds.
/// </summary>
/// <param name="text">Text to display the link as.</param>
/// <param name="url">Url that the link will lead to.</param>
/// <param name="altText">Alt text to display on hover.</param>
/// <returns>Formatted url.</returns>
public static string MaskedUrl(this string text, Uri url, string altText = "")
=> $"[{text}]({url}{(!string.IsNullOrWhiteSpace(altText) ? $" \"{altText}\"" : "")})";
/// <summary>
/// Escapes all markdown formatting from specified text.
/// </summary>
/// <param name="text">Text to sanitize.</param>
/// <returns>Sanitized text.</returns>
public static string Sanitize(this string text)
=> s_mdSanitizeRegex.Replace(text, m => $"\\{m.Groups[1].Value}");
/// <summary>
/// Removes all markdown formatting from specified text.
/// </summary>
/// <param name="text">Text to strip of formatting.</param>
/// <returns>Formatting-stripped text.</returns>
public static string Strip(this string text)
=> s_mdStripRegex.Replace(text, m => string.Empty);
/// <summary>
/// Creates a mention for specified user or member. Can optionally specify to resolve nicknames.
/// </summary>
/// <param name="user">User to create mention for.</param>
/// <param name="nickname">Whether the mention should resolve nicknames or not.</param>
/// <returns>Formatted mention.</returns>
public static string Mention(this DiscordUser user, bool nickname = false)
=> nickname
? $"<@!{user.Id.ToString(CultureInfo.InvariantCulture)}>"
: $"<@{user.Id.ToString(CultureInfo.InvariantCulture)}>";
/// <summary>
/// Creates a mention for specified channel.
/// </summary>
/// <param name="channel">Channel to mention.</param>
/// <returns>Formatted mention.</returns>
public static string Mention(this DiscordChannel channel)
=> $"<#{channel.Id.ToString(CultureInfo.InvariantCulture)}>";
/// <summary>
/// Creates a mention for specified role.
/// </summary>
/// <param name="role">Role to mention.</param>
/// <returns>Formatted mention.</returns>
public static string Mention(this DiscordRole role)
=> $"<@&{role.Id.ToString(CultureInfo.InvariantCulture)}>";
/// <summary>
/// Creates a custom emoji string.
/// </summary>
/// <param name="emoji">Emoji to display.</param>
/// <returns>Formatted emoji.</returns>
public static string Emoji(this DiscordEmoji emoji)
=> $"<:{emoji.Name}:{emoji.Id.ToString(CultureInfo.InvariantCulture)}>";
/// <summary>
/// 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.
/// </summary>
/// <param name="filename">Name of attached image to display</param>
/// <returns></returns>
public static string AttachedImageUrl(this string filename)
=> $"attachment://{filename}";
}
diff --git a/DisCatSharp/GlobalSuppressions.cs b/DisCatSharp/GlobalSuppressions.cs
index 3a4843d39..83ca7a7f7 100644
--- a/DisCatSharp/GlobalSuppressions.cs
+++ b/DisCatSharp/GlobalSuppressions.cs
@@ -1,80 +1,80 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.Net.Abstractions.ClientProperties.OperatingSystem")]
[assembly: SuppressMessage("CodeQuality", "IDE0079:Remove unnecessary suppression", Justification = "<Pending>")]
[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "<Pending>", Scope = "member", Target = "~F:DisCatSharp.Entities.DiscordUnicodeEmoji._1SkinTone1")]
[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "<Pending>", Scope = "member", Target = "~F:DisCatSharp.Entities.DiscordUnicodeEmoji._1SkinTone2")]
[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "<Pending>", Scope = "member", Target = "~F:DisCatSharp.Entities.DiscordUnicodeEmoji._1SkinTone3")]
[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "<Pending>", Scope = "member", Target = "~F:DisCatSharp.Entities.DiscordUnicodeEmoji._1SkinTone4")]
[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "<Pending>", Scope = "member", Target = "~F:DisCatSharp.Entities.DiscordUnicodeEmoji._1SkinTone5")]
[assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "<Pending>", Scope = "member", Target = "~F:DisCatSharp.Entities.DiscordUnicodeEmoji._8ball")]
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.DiscordClient.OnEmbeddedActivityUpdateAsync(Newtonsoft.Json.Linq.JObject,DisCatSharp.Entities.DiscordGuild,System.UInt64,Newtonsoft.Json.Linq.JArray,System.UInt64)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.DiscordClient.UpdateCachedScheduledEvent(DisCatSharp.Entities.DiscordGuild,Newtonsoft.Json.Linq.JArray)")]
[assembly: SuppressMessage("Style", "IDE0150:Prefer 'null' check over type check", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.DiscordClient.OnPresenceUpdateEventAsync(Newtonsoft.Json.Linq.JObject,Newtonsoft.Json.Linq.JObject)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.Internals.s_permissionStrings")]
[assembly: SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.Internals.s_versionHeader")]
[assembly: SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "<Pending>", Scope = "member", Target = "~F:DisCatSharp.DiscordClient._heartbeatTask")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Net.DiscordApiClient.BulkOverwriteGlobalApplicationCommandsAsync(System.UInt64,System.Collections.Generic.IEnumerable{DisCatSharp.Entities.DiscordApplicationCommand})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyList{DisCatSharp.Entities.DiscordApplicationCommand}}")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Net.DiscordApiClient.BulkOverwriteGuildApplicationCommandsAsync(System.UInt64,System.UInt64,System.Collections.Generic.IEnumerable{DisCatSharp.Entities.DiscordApplicationCommand})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyList{DisCatSharp.Entities.DiscordApplicationCommand}}")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.DiscordClient.EventErrorHandler``2(DisCatSharp.Common.Utilities.AsyncEvent{``0,``1},System.Exception,DisCatSharp.Common.Utilities.AsyncEventHandler{``0,``1},``0,``1)")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.DiscordClient.InternalConnectAsync~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.DiscordClient.OnGuildRoleDeleteEventAsync(System.UInt64,DisCatSharp.Entities.DiscordGuild)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.DiscordClient.WsSendAsync(System.String)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.DiscordShardedClient.EventErrorHandler``1(DisCatSharp.Common.Utilities.AsyncEvent{DisCatSharp.DiscordClient,``0},System.Exception,DisCatSharp.Common.Utilities.AsyncEventHandler{DisCatSharp.DiscordClient,``0},DisCatSharp.DiscordClient,``0)")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Net.RestClient.BuildRequest(DisCatSharp.Net.BaseRestRequest)~System.Net.Http.HttpRequestMessage")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Net.RestClient.ExecuteRequestAsync(DisCatSharp.Net.BaseRestRequest,DisCatSharp.Net.RateLimitBucket,System.Threading.Tasks.TaskCompletionSource{System.Boolean})~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.DiscordClient.OnEmbeddedActivityUpdateAsync(Newtonsoft.Json.Linq.JObject,DisCatSharp.Entities.DiscordGuild,System.UInt64,Newtonsoft.Json.Linq.JArray,System.UInt64)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Net.Abstractions.GamePartySizeConverter.ReadArrayObject(Newtonsoft.Json.JsonReader,Newtonsoft.Json.JsonSerializer)~Newtonsoft.Json.Linq.JArray")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Net.Abstractions.ShardInfoConverter.ReadArrayObject(Newtonsoft.Json.JsonReader,Newtonsoft.Json.JsonSerializer)~Newtonsoft.Json.Linq.JArray")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Net.RestClient.Handle429(DisCatSharp.Net.RestResponse,System.Threading.Tasks.Task@,System.Boolean@)")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.Net.Abstractions.ClientProperties.OperatingSystem")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.Net.Abstractions.ClientProperties.Referrer")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~P:DisCatSharp.Net.Abstractions.ClientProperties.ReferringDomain")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.DiscordClient.HandleDispatchAsync(DisCatSharp.Net.Abstractions.GatewayPayload)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.DiscordClient.HandleSocketMessageAsync(System.String)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.DiscordClient.InternalConnectAsync~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.DiscordClient.OnHeartbeatAckAsync~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.DiscordClient.SendIdentifyAsync(DisCatSharp.Net.Abstractions.StatusUpdate)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.DiscordClient.EventErrorHandler``2(DisCatSharp.Common.Utilities.AsyncEvent{``0,``1},System.Exception,DisCatSharp.Common.Utilities.AsyncEventHandler{``0,``1},``0,``1)")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.DiscordClient.Goof``2(DisCatSharp.Common.Utilities.AsyncEvent{``0,``1},System.Exception,DisCatSharp.Common.Utilities.AsyncEventHandler{``0,``1},``0,``1)")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.DiscordClient.ConnectAsync(DisCatSharp.Entities.DiscordActivity,System.Nullable{DisCatSharp.Entities.UserStatus},System.Nullable{System.DateTimeOffset})~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Net.RestClient.CleanupBucketsAsync~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Net.RestClient.ExecuteRequestAsync(DisCatSharp.Net.BaseRestRequest,DisCatSharp.Net.RateLimitBucket,System.Threading.Tasks.TaskCompletionSource{System.Boolean})~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Net.RestClient.UpdateHashCaches(DisCatSharp.Net.BaseRestRequest,DisCatSharp.Net.RateLimitBucket,System.String)")]
[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Net.RestClient.WaitForInitialRateLimit(DisCatSharp.Net.RateLimitBucket)~System.Threading.Tasks.Task{System.Threading.Tasks.TaskCompletionSource{System.Boolean}}")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.DiscordShardedClient.GetGatewayInfoAsync~System.Threading.Tasks.Task{DisCatSharp.Net.GatewayInfo}")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Entities.DisCatSharpTeam.Get(System.Net.Http.HttpClient,Microsoft.Extensions.Logging.ILogger,DisCatSharp.Net.DiscordApiClient)~System.Threading.Tasks.Task{DisCatSharp.Entities.DisCatSharpTeam}")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.DiscordShardedClient.StartAsync~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Utilities.LogTaskFault(System.Threading.Tasks.Task,Microsoft.Extensions.Logging.ILogger,Microsoft.Extensions.Logging.LogLevel,Microsoft.Extensions.Logging.EventId,System.String)")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.DiscordShardedClient.ConnectShardAsync(System.Int32)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.DiscordShardedClient.EventErrorHandler``1(DisCatSharp.Common.Utilities.AsyncEvent{DisCatSharp.DiscordClient,``0},System.Exception,DisCatSharp.Common.Utilities.AsyncEventHandler{DisCatSharp.DiscordClient,``0},DisCatSharp.DiscordClient,``0)")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.DiscordShardedClient.Goof``1(DisCatSharp.Common.Utilities.AsyncEvent{DisCatSharp.DiscordClient,``0},System.Exception,DisCatSharp.Common.Utilities.AsyncEventHandler{DisCatSharp.DiscordClient,``0},DisCatSharp.DiscordClient,``0)")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.DiscordShardedClient.InternalStopAsync(System.Boolean)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Usage", "CA2253:Named placeholders should not be numeric values", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.DiscordShardedClient.StartAsync~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Performance", "CA1826:Do not use Enumerable methods on indexable collections", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Entities.DiscordChannel.GetMessagesInternalAsync(System.Int32,System.Nullable{System.UInt64},System.Nullable{System.UInt64},System.Nullable{System.UInt64})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyList{DisCatSharp.Entities.DiscordMessage}}")]
[assembly: SuppressMessage("Performance", "CA1826:Do not use Enumerable methods on indexable collections", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Entities.DiscordGuild.GetAllMembersAsync~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyCollection{DisCatSharp.Entities.DiscordMember}}")]
[assembly: SuppressMessage("Performance", "CA1826:Do not use Enumerable methods on indexable collections", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.Entities.DiscordMessage.GetReactionsInternalAsync(DisCatSharp.Entities.DiscordEmoji,System.Int32,System.Nullable{System.UInt64})~System.Threading.Tasks.Task{System.Collections.Generic.IReadOnlyList{DisCatSharp.Entities.DiscordUser}}")]
[assembly: SuppressMessage("Style", "IDE0030:Use coalesce expression", Justification = "<Pending>", Scope = "member", Target = "~M:DisCatSharp.DiscordClient.OnInteractionCreateAsync(System.Nullable{System.UInt64},System.UInt64,DisCatSharp.Net.Abstractions.TransportUser,DisCatSharp.Net.Abstractions.TransportMember,DisCatSharp.Entities.DiscordInteraction,System.String)~System.Threading.Tasks.Task")]
[assembly: SuppressMessage("Information", "DCS0001:Experimental", Justification = "<Pending>", Scope = "type", Target = "~T:DisCatSharp.Entities.GuildFeaturesEnum")]
diff --git a/DisCatSharp/ImageTool.cs b/DisCatSharp/ImageTool.cs
index 4266d8e74..a99c60763 100644
--- a/DisCatSharp/ImageTool.cs
+++ b/DisCatSharp/ImageTool.cs
@@ -1,235 +1,235 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Entities;
namespace DisCatSharp;
/// <summary>
/// Tool to detect image formats and convert from binary data to base64 strings.
/// </summary>
public sealed class ImageTool : IDisposable
{
/// <summary>
/// The png magic .
/// </summary>
private const ulong PNG_MAGIC = 0x0A1A_0A0D_474E_5089;
/// <summary>
/// The jpeg magic 1.
/// </summary>
private const ushort JPEG_MAGIC_1 = 0xD8FF;
/// <summary>
/// The jpeg magic 2.
/// </summary>
private const ushort JPEG_MAGIC_2 = 0xD9FF;
/// <summary>
/// The gif magic 1
/// </summary>
private const ulong GIF_MAGIC_1 = 0x0000_6139_3846_4947;
/// <summary>
/// The gif magic 2.
/// </summary>
private const ulong GIF_MAGIC_2 = 0x0000_6137_3846_4947;
/// <summary>
/// The webp magic 1.
/// </summary>
private const uint WEBP_MAGIC_1 = 0x4646_4952;
/// <summary>
/// The webp magic 2.
/// </summary>
private const uint WEBP_MAGIC_2 = 0x5042_4557;
/// <summary>
/// The gif mask.
/// </summary>
private const ulong GIF_MASK = 0x0000_FFFF_FFFF_FFFF;
/// <summary>
/// The mask 32.
/// </summary>
private const ulong MASK32 = 0x0000_0000_FFFF_FFFF;
/// <summary>
/// The mask 16.
/// </summary>
private const uint MASK16 = 0x0000_FFFF;
/// <summary>
/// Gets the stream this tool is operating on.
/// </summary>
public Stream SourceStream { get; }
private ImageFormat _ifcache;
private string _b64Cache;
/// <summary>
/// Creates a new image tool from given stream.
/// </summary>
/// <param name="stream">Stream to work with.</param>
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;
}
/// <summary>
/// Detects the format of this image.
/// </summary>
/// <returns>Detected format.</returns>
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.");
}
/// <summary>
/// Converts this image into base64 data format string.
/// </summary>
/// <returns>Data-scheme base64 string.</returns>
public string GetBase64()
{
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();
}
/// <summary>
/// Disposes this image tool.
/// </summary>
public void Dispose()
=> this.SourceStream?.Dispose();
/// <summary>
/// Utility function to convert an image stream into a base 64 string.
/// </summary>
/// <param name="stream">The stream.</param>
/// <returns>The base 64 string.</returns>
public static string Base64FromStream(Stream stream)
{
using var imgtool = new ImageTool(stream);
return imgtool.GetBase64();
}
/// <summary>
/// Utility function to convert an optional image stream into an optional base 64 string.
/// </summary>
/// <param name="stream">The optional stream.</param>
/// <returns>The optional base 64 string.</returns>
public static Optional<string> Base64FromStream(Optional<Stream> stream)
{
if (stream.HasValue)
{
var val = stream.Value;
return val != null ? Base64FromStream(val) : null;
}
return Optional.None;
}
}
/// <summary>
/// Represents format of an image.
/// </summary>
public enum ImageFormat : int
{
/// <summary>
/// The format is unknown
/// </summary>
Unknown = 0,
/// <summary>
/// The format is a jpeg
/// </summary>
Jpeg = 1,
/// <summary>
/// The format is a png
/// </summary>
Png = 2,
/// <summary>
/// The format is a gif
/// </summary>
Gif = 3,
/// <summary>
/// The format is a webp
/// </summary>
WebP = 4,
/// <summary>
/// The format will be automatically detected
/// </summary>
Auto = 5
}
diff --git a/DisCatSharp/Internals.cs b/DisCatSharp/Internals.cs
index 8dab6cc0a..4bd018a6d 100644
--- a/DisCatSharp/Internals.cs
+++ b/DisCatSharp/Internals.cs
@@ -1,95 +1,95 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
using DisCatSharp.Enums;
namespace DisCatSharp;
/// <summary>
/// Internal tools.
/// </summary>
public static class Internals
{
/// <summary>
/// Gets the version of the library
/// </summary>
private static string s_versionHeader
=> Utilities.VersionHeader;
/// <summary>
/// Gets the permission strings.
/// </summary>
private static Dictionary<Permissions, string> s_permissionStrings
=> Utilities.PermissionStrings;
/// <summary>
/// Gets the utf8 encoding
/// </summary>
internal static UTF8Encoding Utf8
=> Utilities.UTF8;
/// <summary>
/// Initializes a new instance of the <see cref="Internals"/> class.
/// </summary>
static Internals() { }
/// <summary>
/// Whether the <see cref="DiscordChannel"/> is joinable via voice.
/// </summary>
/// <param name="channel">The channel.</param>
internal static bool IsVoiceJoinable(this DiscordChannel channel) => channel.Type == ChannelType.Voice || channel.Type == ChannelType.Stage;
/// <summary>
/// Whether the <see cref="DiscordChannel"/> can have threads.
/// </summary>
/// <param name="channel">The channel.</param>
internal static bool IsThreadHolder(this DiscordChannel channel) => channel.Type == ChannelType.Text || channel.Type == ChannelType.News || channel.Type == ChannelType.Forum;
/// <summary>
/// Whether the <see cref="DiscordChannel"/> is related to threads.
/// </summary>
/// <param name="channel">The channel.</param>
internal static bool IsThread(this DiscordChannel channel) => channel.Type == ChannelType.PublicThread || channel.Type == ChannelType.PrivateThread || channel.Type == ChannelType.NewsThread;
/// <summary>
/// Whether users can write the <see cref="DiscordChannel"/>.
/// </summary>
/// <param name="channel">The channel.</param>
internal static bool IsWritable(this DiscordChannel channel) => channel.Type is
ChannelType.PublicThread or ChannelType.PrivateThread or ChannelType.NewsThread or ChannelType.Text or ChannelType.News or ChannelType.Group or ChannelType.Private or ChannelType.Voice;
/// <summary>
/// Whether the <see cref="DiscordChannel"/> is moveable in a parent.
/// </summary>
/// <param name="channel">The channel.</param>
internal static bool IsMovableInParent(this DiscordChannel channel) => channel.Type == ChannelType.Voice || channel.Type == ChannelType.Stage || channel.Type == ChannelType.Text || channel.Type == ChannelType.Forum || channel.Type == ChannelType.News;
/// <summary>
/// Whether the <see cref="DiscordChannel"/> is moveable.
/// </summary>
/// <param name="channel">The channel.</param>
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.Forum || channel.Type == ChannelType.News;
}
diff --git a/DisCatSharp/Logging/CompositeDefaultLogger.cs b/DisCatSharp/Logging/CompositeDefaultLogger.cs
index 4a95ad018..372dfb805 100644
--- a/DisCatSharp/Logging/CompositeDefaultLogger.cs
+++ b/DisCatSharp/Logging/CompositeDefaultLogger.cs
@@ -1,78 +1,78 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 Microsoft.Extensions.Logging;
namespace DisCatSharp;
/// <summary>
/// Represents a composite default logger.
/// </summary>
internal class CompositeDefaultLogger : ILogger<BaseDiscordClient>
{
/// <summary>
/// Gets the loggers.
/// </summary>
private readonly List<ILogger<BaseDiscordClient>> _loggers;
/// <summary>
/// Initializes a new instance of the <see cref="CompositeDefaultLogger"/> class.
/// </summary>
/// <param name="providers">The providers.</param>
public CompositeDefaultLogger(IEnumerable<ILoggerProvider> providers)
{
this._loggers = providers.Select(x => x.CreateLogger(typeof(BaseDiscordClient).FullName))
.OfType<ILogger<BaseDiscordClient>>()
.ToList();
}
/// <summary>
/// Whether the logger is enabled.
/// </summary>
/// <param name="logLevel">The log level.</param>
public bool IsEnabled(LogLevel logLevel)
=> true;
/// <summary>
/// Logs an event.
/// </summary>
/// <param name="logLevel">The log level.</param>
/// <param name="eventId">The event id.</param>
/// <param name="state">The state.</param>
/// <param name="exception">The exception.</param>
/// <param name="formatter">The formatter.</param>
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
foreach (var logger in this._loggers)
logger.Log(logLevel, eventId, state, exception, formatter);
}
/// <summary>
/// Begins the scope.
/// </summary>
/// <param name="state">The state.</param>
public IDisposable BeginScope<TState>(TState state) => throw new NotImplementedException();
}
diff --git a/DisCatSharp/Logging/DefaultLogger.cs b/DisCatSharp/Logging/DefaultLogger.cs
index dc8462c20..5b601d871 100644
--- a/DisCatSharp/Logging/DefaultLogger.cs
+++ b/DisCatSharp/Logging/DefaultLogger.cs
@@ -1,148 +1,148 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a default logger.
/// </summary>
public class DefaultLogger : ILogger<BaseDiscordClient>
{
private static readonly object s_lock = new();
/// <summary>
/// Gets the minimum log level.
/// </summary>
private readonly LogLevel _minimumLevel;
/// <summary>
/// Gets the timestamp format.
/// </summary>
private readonly string _timestampFormat;
/// <summary>
/// Initializes a new instance of the <see cref="DefaultLogger"/> class.
/// </summary>
/// <param name="client">The client.</param>
internal DefaultLogger(BaseDiscordClient client)
: this(client.Configuration.MinimumLogLevel, client.Configuration.LogTimestampFormat)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="DefaultLogger"/> class.
/// </summary>
/// <param name="minLevel">The min level.</param>
/// <param name="timestampFormat">The timestamp format.</param>
internal DefaultLogger(LogLevel minLevel = LogLevel.Information, string timestampFormat = "yyyy-MM-dd HH:mm:ss zzz")
{
this._minimumLevel = minLevel;
this._timestampFormat = timestampFormat;
}
/// <summary>
/// Logs an event.
/// </summary>
/// <param name="logLevel">The log level.</param>
/// <param name="eventId">The event id.</param>
/// <param name="state">The state.</param>
/// <param name="exception">The exception.</param>
/// <param name="formatter">The formatter.</param>
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (!this.IsEnabled(logLevel))
return;
lock (s_lock)
{
var ename = eventId.Name;
ename = ename?.Length > 12 ? ename?[..12] : ename;
Console.Write($"[{DateTimeOffset.Now.ToString(this._timestampFormat)}] [{eventId.Id,-4}/{ename,-12}] ");
switch (logLevel)
{
case LogLevel.Trace:
Console.ForegroundColor = ConsoleColor.Gray;
break;
case LogLevel.Debug:
Console.ForegroundColor = ConsoleColor.DarkMagenta;
break;
case LogLevel.Information:
Console.ForegroundColor = ConsoleColor.DarkCyan;
break;
case LogLevel.Warning:
Console.ForegroundColor = ConsoleColor.Yellow;
break;
case LogLevel.Error:
Console.ForegroundColor = ConsoleColor.Red;
break;
case LogLevel.Critical:
Console.BackgroundColor = ConsoleColor.Red;
Console.ForegroundColor = ConsoleColor.Black;
break;
}
Console.Write(logLevel switch
{
LogLevel.Trace => "[Trace] ",
LogLevel.Debug => "[Debug] ",
LogLevel.Information => "[Info ] ",
LogLevel.Warning => "[Warn ] ",
LogLevel.Error => "[Error] ",
LogLevel.Critical => "[Critical ]",
LogLevel.None => "[None ] ",
_ => "[?????] "
});
Console.ResetColor();
//The foreground color is off.
if (logLevel == LogLevel.Critical)
Console.Write(" ");
var message = formatter(state, exception);
Console.WriteLine(message);
if (exception != null)
Console.WriteLine(exception);
}
}
/// <summary>
/// Whether the logger is enabled.
/// </summary>
/// <param name="logLevel">The log level.</param>
public bool IsEnabled(LogLevel logLevel)
=> logLevel >= this._minimumLevel;
/// <summary>
/// Begins the scope.
/// </summary>
/// <param name="state">The state.</param>
/// <returns>An IDisposable.</returns>
public IDisposable BeginScope<TState>(TState state) => throw new NotImplementedException();
}
diff --git a/DisCatSharp/Logging/DefaultLoggerFactory.cs b/DisCatSharp/Logging/DefaultLoggerFactory.cs
index e25097d00..2fdca9714 100644
--- a/DisCatSharp/Logging/DefaultLoggerFactory.cs
+++ b/DisCatSharp/Logging/DefaultLoggerFactory.cs
@@ -1,72 +1,72 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 Microsoft.Extensions.Logging;
namespace DisCatSharp;
/// <summary>
/// Represents a default logger factory.
/// </summary>
internal class DefaultLoggerFactory : ILoggerFactory
{
/// <summary>
/// Gets the providers.
/// </summary>
private readonly List<ILoggerProvider> _providers = new();
private bool _isDisposed;
/// <summary>
/// Adds a provider.
/// </summary>
/// <param name="provider">The provider to be added.</param>
public void AddProvider(ILoggerProvider provider) => this._providers.Add(provider);
/// <summary>
/// Creates the logger.
/// </summary>
/// <param name="categoryName">The category name.</param>
public ILogger CreateLogger(string categoryName) =>
this._isDisposed
? throw new InvalidOperationException("This logger factory is already disposed.")
: categoryName != typeof(BaseDiscordClient).FullName && categoryName != typeof(DiscordWebhookClient).FullName
? throw new ArgumentException($"This factory can only provide instances of loggers for {typeof(BaseDiscordClient).FullName} or {typeof(DiscordWebhookClient).FullName}.", nameof(categoryName))
: new CompositeDefaultLogger(this._providers);
/// <summary>
/// Disposes the logger.
/// </summary>
public void Dispose()
{
if (this._isDisposed)
return;
this._isDisposed = true;
foreach (var provider in this._providers)
provider.Dispose();
this._providers.Clear();
}
}
diff --git a/DisCatSharp/Logging/DefaultLoggerProvider.cs b/DisCatSharp/Logging/DefaultLoggerProvider.cs
index 5f816a6e7..8866d36b0 100644
--- a/DisCatSharp/Logging/DefaultLoggerProvider.cs
+++ b/DisCatSharp/Logging/DefaultLoggerProvider.cs
@@ -1,88 +1,88 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a default logger provider.
/// </summary>
internal class DefaultLoggerProvider : ILoggerProvider
{
/// <summary>
/// Gets the minimum log level.
/// </summary>
private readonly LogLevel _minimumLevel;
/// <summary>
/// Gets the timestamp format.
/// </summary>
private readonly string _timestampFormat;
private bool _isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="DefaultLoggerProvider"/> class.
/// </summary>
/// <param name="client">The client.</param>
internal DefaultLoggerProvider(BaseDiscordClient client)
: this(client.Configuration.MinimumLogLevel, client.Configuration.LogTimestampFormat)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="DefaultLoggerProvider"/> class.
/// </summary>
/// <param name="client">The client.</param>
internal DefaultLoggerProvider(DiscordWebhookClient client)
: this(client.MinimumLogLevel, client.LogTimestampFormat)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="DefaultLoggerProvider"/> class.
/// </summary>
/// <param name="minLevel">The min level.</param>
/// <param name="timestampFormat">The timestamp format.</param>
internal DefaultLoggerProvider(LogLevel minLevel = LogLevel.Information, string timestampFormat = "yyyy-MM-dd HH:mm:ss zzz")
{
this._minimumLevel = minLevel;
this._timestampFormat = timestampFormat;
}
/// <summary>
/// Creates the logger.
/// </summary>
/// <param name="categoryName">The category name.</param>
public ILogger CreateLogger(string categoryName) =>
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);
/// <summary>
/// Disposes the logger.
/// </summary>
public void Dispose() => this._isDisposed = true;
}
diff --git a/DisCatSharp/Logging/LoggerEvents.cs b/DisCatSharp/Logging/LoggerEvents.cs
index 204120a3f..1a60e70d4 100644
--- a/DisCatSharp/Logging/LoggerEvents.cs
+++ b/DisCatSharp/Logging/LoggerEvents.cs
@@ -1,171 +1,171 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 Microsoft.Extensions.Logging;
namespace DisCatSharp;
/// <summary>
/// Contains well-defined event IDs used by core of DisCatSharp.
/// </summary>
public static class LoggerEvents
{
/// <summary>
/// Miscellaneous events, that do not fit in any other category.
/// </summary>
public static EventId Misc { get; } = new(100, "DisCatSharp");
/// <summary>
/// Events pertaining to startup tasks.
/// </summary>
public static EventId Startup { get; } = new(101, nameof(Startup));
/// <summary>
/// Events typically emitted whenever WebSocket connections fail or are terminated.
/// </summary>
public static EventId ConnectionFailure { get; } = new(102, nameof(ConnectionFailure));
/// <summary>
/// Events pertaining to Discord-issued session state updates.
/// </summary>
public static EventId SessionUpdate { get; } = new(103, nameof(SessionUpdate));
/// <summary>
/// Events emitted when exceptions are thrown in handlers attached to async events.
/// </summary>
public static EventId EventHandlerException { get; } = new(104, nameof(EventHandlerException));
/// <summary>
/// Events emitted for various high-level WebSocket receive events.
/// </summary>
public static EventId WebSocketReceive { get; } = new(105, nameof(WebSocketReceive));
/// <summary>
/// Events emitted for various low-level WebSocket receive events.
/// </summary>
public static EventId WebSocketReceiveRaw { get; } = new(106, nameof(WebSocketReceiveRaw));
/// <summary>
/// Events emitted for various low-level WebSocket send events.
/// </summary>
public static EventId WebSocketSendRaw { get; } = new(107, nameof(WebSocketSendRaw));
/// <summary>
/// Events emitted for various WebSocket payload processing failures, typically when deserialization or decoding fails.
/// </summary>
public static EventId WebSocketReceiveFailure { get; } = new(108, nameof(WebSocketReceiveFailure));
/// <summary>
/// Events pertaining to connection lifecycle, specifically, heartbeats.
/// </summary>
public static EventId Heartbeat { get; } = new(109, nameof(Heartbeat));
/// <summary>
/// Events pertaining to various heartbeat failures, typically fatal.
/// </summary>
public static EventId HeartbeatFailure { get; } = new(110, nameof(HeartbeatFailure));
/// <summary>
/// Events pertaining to clean connection closes.
/// </summary>
public static EventId ConnectionClose { get; } = new(111, nameof(ConnectionClose));
/// <summary>
/// Events emitted when REST processing fails for any reason.
/// </summary>
public static EventId RestError { get; } = new(112, nameof(RestError));
/// <summary>
/// Events pertaining to the <see cref="DiscordShardedClient"/> shard startup.
/// </summary>
public static EventId ShardStartup { get; } = new(113, nameof(ShardStartup));
/// <summary>
/// Events pertaining to ratelimit exhaustion.
/// </summary>
public static EventId RatelimitHit { get; } = new(114, nameof(RatelimitHit));
/// <summary>
/// Events pertaining to ratelimit diagnostics. Typically contain raw bucket info.
/// </summary>
public static EventId RatelimitDiag { get; } = new(115, nameof(RatelimitDiag));
/// <summary>
/// Events emitted when a ratelimit is exhausted and a request is preemptively blocked.
/// </summary>
public static EventId RatelimitPreemptive { get; } = new(116, nameof(RatelimitPreemptive));
/// <summary>
/// Events pertaining to audit log processing.
/// </summary>
public static EventId AuditLog { get; } = new(117, nameof(AuditLog));
/// <summary>
/// Events containing raw (but decompressed) payloads, received from Discord Gateway.
/// </summary>
public static EventId GatewayWsRx { get; } = new(118, "Gateway ↓");
/// <summary>
/// Events containing raw payloads, as they're being sent to Discord Gateway.
/// </summary>
public static EventId GatewayWsTx { get; } = new(119, "Gateway ↑");
/// <summary>
/// Events pertaining to Gateway Intents. Typically diagnostic information.
/// </summary>
public static EventId Intents { get; } = new(120, nameof(Intents));
/// <summary>
/// Events pertaining to autosharded client shard shutdown, clean or otherwise.
/// </summary>
public static EventId ShardShutdown { get; } = new(121, nameof(ShardShutdown));
/// <summary>
/// Events pertaining to the <see cref="DiscordShardedClient"/>'s shards not initializing correctly.
/// </summary>
public static EventId ShardClientError { get; } = new(122, nameof(ShardClientError));
/// <summary>
/// Events containing raw payloads, as they're received from Discord's REST API.
/// </summary>
public static EventId RestRx { get; } = new(123, "REST ↓");
/// <summary>
/// Events containing raw payloads, as they're sent to Discord's REST API.
/// </summary>
public static EventId RestTx { get; } = new(124, "REST ↑");
/// <summary>
/// Event is rest cleaner.
/// </summary>
public static EventId RestCleaner { get; } = new(125, nameof(RestCleaner));
/// <summary>
/// Event is rest hash mover.
/// </summary>
public static EventId RestHashMover { get; } = new(126, nameof(RestHashMover));
/// <summary>
/// Events pertaining to Discord API requests from the <see cref="DiscordShardedClient"/>.
/// </summary>
public static EventId ShardRest { get; } = new(127, nameof(ShardRest));
}
diff --git a/DisCatSharp/Logging/ShardedLoggerFactory.cs b/DisCatSharp/Logging/ShardedLoggerFactory.cs
index 8bf85bdca..e946834c5 100644
--- a/DisCatSharp/Logging/ShardedLoggerFactory.cs
+++ b/DisCatSharp/Logging/ShardedLoggerFactory.cs
@@ -1,68 +1,68 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a sharded logger factory.
/// </summary>
internal class ShardedLoggerFactory : ILoggerFactory
{
/// <summary>
/// Gets the logger.
/// </summary>
private readonly ILogger<BaseDiscordClient> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="ShardedLoggerFactory"/> class.
/// </summary>
/// <param name="instance">The instance.</param>
public ShardedLoggerFactory(ILogger<BaseDiscordClient> instance)
{
this._logger = instance;
}
/// <summary>
/// Adds a provider.
/// </summary>
/// <param name="provider">The provider to be added.</param>
public void AddProvider(ILoggerProvider provider) => throw new InvalidOperationException("This is a passthrough logger container, it cannot register new providers.");
/// <summary>
/// Creates a logger.
/// </summary>
/// <param name="categoryName">The category name.</param>
public ILogger CreateLogger(string categoryName) =>
categoryName != typeof(BaseDiscordClient).FullName
? throw new ArgumentException($"This factory can only provide instances of loggers for {typeof(BaseDiscordClient).FullName}.", nameof(categoryName))
: this._logger;
/// <summary>
/// Disposes the logger.
/// </summary>
public void Dispose()
{ }
}
diff --git a/DisCatSharp/Net/Abstractions/AuditLogAbstractions.cs b/DisCatSharp/Net/Abstractions/AuditLogAbstractions.cs
index 6d9a19ea5..b28539f71 100644
--- a/DisCatSharp/Net/Abstractions/AuditLogAbstractions.cs
+++ b/DisCatSharp/Net/Abstractions/AuditLogAbstractions.cs
@@ -1,576 +1,576 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace DisCatSharp.Net.Abstractions;
/// <summary>
/// Represents a audit log user.
/// </summary>
internal sealed class AuditLogUser
{
/// <summary>
/// Gets or sets the username.
/// </summary>
[JsonProperty("username")]
public string Username { get; set; }
/// <summary>
/// Gets or sets the discriminator.
/// </summary>
[JsonProperty("discriminator")]
public string Discriminator { get; set; }
/// <summary>
/// Gets or sets the id.
/// </summary>
[JsonProperty("id")]
public ulong Id { get; set; }
/// <summary>
/// Gets or sets the avatar hash.
/// </summary>
[JsonProperty("avatar")]
public string AvatarHash { get; set; }
}
/// <summary>
/// Represents a audit log webhook.
/// </summary>
internal sealed class AuditLogWebhook
{
/// <summary>
/// Gets or sets the name.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Gets or sets the channel id.
/// </summary>
[JsonProperty("channel_id")]
public ulong ChannelId { get; set; }
/// <summary>
/// Gets or sets the token.
/// </summary>
[JsonProperty("token")]
public string Token { get; set; }
/// <summary>
/// Gets or sets the avatar hash.
/// </summary>
[JsonProperty("avatar")]
public string AvatarHash { get; set; }
/// <summary>
/// Gets or sets the guild id.
/// </summary>
[JsonProperty("guild_id")]
public ulong GuildId { get; set; }
/// <summary>
/// Gets or sets the id.
/// </summary>
[JsonProperty("id")]
public ulong Id { get; set; }
}
/// <summary>
/// Represents a audit log thread metadata.
/// </summary>
internal sealed class AuditLogThreadMetadata
{
/// <summary>
/// Gets whether the thread is archived.
/// </summary>
[JsonProperty("archived")]
public bool Archived { get; set; }
/// <summary>
/// Gets the threads archive timestamp.
/// </summary>
[JsonProperty("archive_timestamp", NullValueHandling = NullValueHandling.Ignore)]
public string ArchiveTimestamp { get; set; }
/// <summary>
/// Gets the threads auto archive duration.
/// </summary>
[JsonProperty("auto_archive_duration")]
public int AutoArchiveDuration { get; set; }
/// <summary>
/// Gets whether the thread is locked.
/// </summary>
[JsonProperty("locked")]
public bool Locked { get; set; }
}
/// <summary>
/// Represents a audit log thread.
/// </summary>
internal sealed class AuditLogThread
{
/// <summary>
/// Gets the thread id.
/// </summary>
[JsonProperty("id")]
public ulong Id { get; set; }
/// <summary>
/// Gets the thread guild id.
/// </summary>
[JsonProperty("guild_id")]
public ulong GuildId { get; set; }
/// <summary>
/// Gets the thread parent channel id.
/// </summary>
[JsonProperty("parent_id")]
public ulong ParentId { get; set; }
/// <summary>
/// Gets the thread owner id.
/// </summary>
[JsonProperty("owner_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? OwnerId { get; set; }
/// <summary>
/// Gets the thread type.
/// </summary>
[JsonProperty("type")]
public ChannelType Type { get; set; }
/// <summary>
/// Gets the thread name.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Gets the thread last message id.
/// </summary>
[JsonProperty("last_message_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? LastMessageId { get; set; }
/// <summary>
/// Gets the thread metadata.
/// </summary>
[JsonProperty("thread_metadata")]
public AuditLogThreadMetadata Metadata { get; set; }
/// <summary>
/// Gets the thread approximate message count.
/// </summary>
[JsonProperty("message_count", NullValueHandling = NullValueHandling.Ignore)]
public int? MessageCount { get; set; }
/// <summary>
/// Gets the thread member count.
/// </summary>
[JsonProperty("member_count", NullValueHandling = NullValueHandling.Ignore)]
public int? MemberCount { get; set; }
/// <summary>
/// Gets the thread rate limit per user.
/// </summary>
[JsonProperty("rate_limit_per_user", NullValueHandling = NullValueHandling.Ignore)]
public int? RateLimitPerUser { get; set; }
}
/// <summary>
/// Represents a audit log scheduled event.
/// </summary>
internal sealed class AuditLogGuildScheduledEvent
{
/// <summary>
/// Gets the scheduled event id.
/// </summary>
[JsonProperty("id")]
public ulong Id { get; set; }
/// <summary>
/// Gets the scheduled event guild id.
/// </summary>
[JsonProperty("guild_id")]
public ulong GuildId { get; set; }
/// <summary>
/// Gets the scheduled event channel id.
/// </summary>
[JsonProperty("channel_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong ChannelId { get; set; }
/// <summary>
/// Gets the scheduled event creator id.
/// </summary>
[JsonProperty("creator_id")]
public ulong CreatorId { get; set; }
/// <summary>
/// Gets the scheduled event name.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Gets the scheduled event description.
/// </summary>
[JsonProperty("description")]
public string Description { get; set; }
/// <summary>
/// Gets the scheduled event image.
/// </summary>
[JsonProperty("image", NullValueHandling = NullValueHandling.Ignore)]
public string Image { get; set; }
/// <summary>
/// Gets the scheduled event scheduled start time.
/// </summary>
[JsonProperty("scheduled_start_time")]
public string ScheduledStartTime;
/// <summary>
/// Gets the scheduled event scheduled end time.
/// </summary>
[JsonProperty("scheduled_end_time")]
public string ScheduledEndTime { get; set; }
/// <summary>
/// Gets the scheduled event privacy level.
/// </summary>
[JsonProperty("privacy_level")]
public ScheduledEventPrivacyLevel PrivacyLevel { get; set; }
/// <summary>
/// Gets the scheduled event status.
/// </summary>
[JsonProperty("status")]
public ScheduledEventStatus Status { get; set; }
/// <summary>
/// Gets the scheduled event entity type.
/// </summary>
[JsonProperty("entity_type")]
public ScheduledEventEntityType EntityType { get; set; }
/// <summary>
/// Gets the scheduled event entity id.
/// </summary>
[JsonProperty("entity_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong EntityId { get; set; }
/// <summary>
/// Gets the scheduled event entity metadata.
/// </summary>
[JsonProperty("entity_metadata")]
public AuditLogGuildScheduledEventEntityMetadata EntityMetadata { get; set; }
/// <summary>
/// Gets the scheduled event sku ids.
/// </summary>
[JsonProperty("sku_ids")]
public List<ulong> SkuIds { get; set; }
}
/// <summary>
/// Represents a audit log scheduled event entity metadata.
/// </summary>
internal sealed class AuditLogGuildScheduledEventEntityMetadata
{
/// <summary>
/// Gets the scheduled events external location.
/// </summary>
[JsonProperty("location")]
public string Location { get; set; }
}
/// <summary>
/// Represents a audit log integration account.
/// </summary>
internal sealed class AuditLogIntegrationAccount
{
/// <summary>
/// Gets the account id.
/// </summary>
[JsonProperty("id")]
public string Id { get; set; }
/// <summary>
/// Gets the account name.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
}
/// <summary>
/// Represents a audit log integration.
/// </summary>
internal sealed class AuditLogIntegration
{
/// <summary>
/// Gets the integration id.
/// </summary>
[JsonProperty("id")]
public ulong Id { get; set; }
/// <summary>
/// Gets the integration type.
/// </summary>
[JsonProperty("type")]
public string Type { get; set; }
/// <summary>
/// Gets the integration name.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Gets the integration account.
/// </summary>
[JsonProperty("account")]
public AuditLogIntegrationAccount Account { get; set; }
}
/// <summary>
/// Represents a audit log action change.
/// </summary>
internal sealed class AuditLogActionChange
{
// this can be a string or an array
/// <summary>
/// Gets or sets the old value.
/// </summary>
[JsonProperty("old_value")]
public object OldValue { get; set; }
/// <summary>
/// Gets the old values.
/// </summary>
[JsonIgnore]
public IReadOnlyList<JObject> OldValues
=> (this.OldValue as JArray)?.ToObject<IReadOnlyList<JObject>>();
/// <summary>
/// Gets the old value ulong.
/// </summary>
[JsonIgnore]
public ulong OldValueUlong
=> (ulong)this.OldValue;
/// <summary>
/// Gets the old value string.
/// </summary>
[JsonIgnore]
public string OldValueString
=> (string)this.OldValue;
// this can be a string or an array
/// <summary>
/// Gets or sets the new value.
/// </summary>
[JsonProperty("new_value")]
public object NewValue { get; set; }
/// <summary>
/// Gets the new values.
/// </summary>
[JsonIgnore]
public IReadOnlyList<JObject> NewValues
=> (this.NewValue as JArray)?.ToObject<IReadOnlyList<JObject>>();
/// <summary>
/// Gets the new value ulong.
/// </summary>
[JsonIgnore]
public ulong NewValueUlong
=> (ulong)this.NewValue;
/// <summary>
/// Gets the new value string.
/// </summary>
[JsonIgnore]
public string NewValueString
=> (string)this.NewValue;
/// <summary>
/// Gets or sets the key.
/// </summary>
[JsonProperty("key")]
public string Key { get; set; }
}
/// <summary>
/// Represents a audit log action options.
/// </summary>
internal sealed class AuditLogActionOptions
{
/// <summary>
/// Gets or sets the type.
/// </summary>
[JsonProperty("type")]
public object Type { get; set; }
/// <summary>
/// Gets or sets the id.
/// </summary>
[JsonProperty("id")]
public ulong Id { get; set; }
/// <summary>
/// Gets or sets the channel id.
/// </summary>
[JsonProperty("channel_id")]
public ulong ChannelId { get; set; }
/// <summary>
/// Gets or sets the message id.
/// </summary>
[JsonProperty("message_id")]
public ulong MessageId { get; set; }
/// <summary>
/// Gets or sets the count.
/// </summary>
[JsonProperty("count")]
public int Count { get; set; }
/// <summary>
/// Gets or sets the delete member days.
/// </summary>
[JsonProperty("delete_member_days")]
public int DeleteMemberDays { get; set; }
/// <summary>
/// Gets or sets the members removed.
/// </summary>
[JsonProperty("members_removed")]
public int MembersRemoved { get; set; }
}
/// <summary>
/// Represents a audit log action.
/// </summary>
internal sealed class AuditLogAction
{
/// <summary>
/// Gets or sets the target id.
/// </summary>
[JsonProperty("target_id")]
public ulong? TargetId { get; set; }
/// <summary>
/// Gets or sets the user id.
/// </summary>
[JsonProperty("user_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong UserId { get; set; }
/// <summary>
/// Gets or sets the id.
/// </summary>
[JsonProperty("id")]
public ulong Id { get; set; }
/// <summary>
/// Gets or sets the action type.
/// </summary>
[JsonProperty("action_type", NullValueHandling = NullValueHandling.Ignore)]
public AuditLogActionType ActionType { get; set; }
/// <summary>
/// Gets or sets the changes.
/// </summary>
[JsonProperty("changes")]
public IReadOnlyList<AuditLogActionChange> Changes { get; set; }
/// <summary>
/// Gets or sets the options.
/// </summary>
[JsonProperty("options")]
public AuditLogActionOptions Options { get; set; }
/// <summary>
/// Gets or sets the reason.
/// </summary>
[JsonProperty("reason")]
public string Reason { get; set; }
}
/// <summary>
/// Represents a audit log.
/// </summary>
internal sealed class AuditLog
{
/// <summary>
/// Gets or sets the webhooks.
/// </summary>
[JsonProperty("webhooks")]
public IReadOnlyList<AuditLogWebhook> Webhooks { get; set; }
/// <summary>
/// Gets or sets the users.
/// </summary>
[JsonProperty("users")]
public IReadOnlyList<AuditLogUser> Users { get; set; }
/// <summary>
/// Gets or sets the entries.
/// </summary>
[JsonProperty("audit_log_entries")]
public IReadOnlyList<AuditLogAction> Entries { get; set; }
/// <summary>
/// Gets or sets the scheduled events.
/// </summary>
[JsonProperty("guild_scheduled_events")]
public IReadOnlyList<AuditLogGuildScheduledEvent> ScheduledEvents { get; set; }
/// <summary>
/// Gets or sets the threads.
/// </summary>
[JsonProperty("threads")]
public IReadOnlyList<AuditLogThread> Threads { get; set; }
/// <summary>
/// Gets or sets the integrations.
/// Twitch related.
/// </summary>
[JsonProperty("integrations")]
public IReadOnlyList<AuditLogIntegration> Integrations { get; set; }
/*
/// <summary>
/// Gets or sets the application commands.
/// Related to Permissions V2.
/// </summary>
[JsonProperty("application_commands")]
public IReadOnlyList<AuditLogApplicationCommand> ApplicationCommands { get; set; }
*/
}
diff --git a/DisCatSharp/Net/Abstractions/ClientProperties.cs b/DisCatSharp/Net/Abstractions/ClientProperties.cs
index 04a2d67b4..ada7dc8b7 100644
--- a/DisCatSharp/Net/Abstractions/ClientProperties.cs
+++ b/DisCatSharp/Net/Abstractions/ClientProperties.cs
@@ -1,113 +1,113 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Reflection;
using System.Runtime.InteropServices;
using Newtonsoft.Json;
namespace DisCatSharp.Net.Abstractions;
/// <summary>
/// Represents data for identify payload's client properties.
/// </summary>
internal sealed class ClientProperties
{
/// <summary>
/// Gets or sets the discord client.
/// </summary>
[JsonIgnore]
public BaseDiscordClient Discord { get; set; }
/// <summary>
/// Gets the client's operating system.
/// </summary>
[JsonProperty("os")]
public string OperatingSystem
{
get
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return "windows";
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
return "linux";
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
return "osx";
var plat = RuntimeInformation.OSDescription.ToLowerInvariant();
if (plat.Contains("freebsd"))
return "freebsd";
else if (plat.Contains("openbsd"))
return "openbsd";
else if (plat.Contains("netbsd"))
return "netbsd";
else if (plat.Contains("dragonfly"))
return "dragonflybsd";
else if (plat.Contains("miros bsd") || plat.Contains("mirbsd"))
return "miros bsd";
else if (plat.Contains("desktopbsd"))
return "desktopbsd";
else return plat.Contains("darwin") ? "osx" : plat.Contains("unix") ? "unix" : "toaster (unknown)";
}
}
/// <summary>
/// Gets the client's browser.
/// </summary>
[JsonProperty("browser")]
public string Browser
{
get
{
if (this.Discord.Configuration.MobileStatus)
return "Discord Android";
else
{
var a = typeof(DiscordClient).GetTypeInfo().Assembly;
var an = a.GetName();
return $"DisCatSharp {an.Version.ToString(4)}";
}
}
}
/// <summary>
/// Gets the client's device.
/// </summary>
[JsonProperty("device")]
public string Device
=> this.Browser;
/// <summary>
/// Gets the client's referrer.
/// </summary>
[JsonProperty("referrer")]
public string Referrer
=> "";
/// <summary>
/// Gets the client's referring domain.
/// </summary>
[JsonProperty("referring_domain")]
public string ReferringDomain
=> "";
}
diff --git a/DisCatSharp/Net/Abstractions/FollowedChannelAddPayload.cs b/DisCatSharp/Net/Abstractions/FollowedChannelAddPayload.cs
index 82644bafc..08a21090c 100644
--- a/DisCatSharp/Net/Abstractions/FollowedChannelAddPayload.cs
+++ b/DisCatSharp/Net/Abstractions/FollowedChannelAddPayload.cs
@@ -1,37 +1,37 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net.Abstractions;
/// <summary>
/// Represents a followed channel add payload.
/// </summary>
internal sealed class FollowedChannelAddPayload
{
/// <summary>
/// Gets or sets the webhook channel id.
/// </summary>
[JsonProperty("webhook_channel_id")]
public ulong WebhookChannelId { get; set; }
}
diff --git a/DisCatSharp/Net/Abstractions/Gateway/GatewayHello.cs b/DisCatSharp/Net/Abstractions/Gateway/GatewayHello.cs
index 084507917..e61cd9d8d 100644
--- a/DisCatSharp/Net/Abstractions/Gateway/GatewayHello.cs
+++ b/DisCatSharp/Net/Abstractions/Gateway/GatewayHello.cs
@@ -1,45 +1,45 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net.Abstractions;
/// <summary>
/// Represents data for a websocket hello payload.
/// </summary>
internal sealed class GatewayHello
{
/// <summary>
/// Gets the target heartbeat interval (in milliseconds) requested by Discord.
/// </summary>
[JsonProperty("heartbeat_interval")]
public int HeartbeatInterval { get; private set; }
/// <summary>
/// Gets debug data sent by Discord. This contains a list of servers to which the client is connected.
/// </summary>
[JsonProperty("_trace")]
public IReadOnlyList<string> Trace { get; private set; }
}
diff --git a/DisCatSharp/Net/Abstractions/Gateway/GatewayIdentifyResume.cs b/DisCatSharp/Net/Abstractions/Gateway/GatewayIdentifyResume.cs
index 0b910d7a0..eecf4d307 100644
--- a/DisCatSharp/Net/Abstractions/Gateway/GatewayIdentifyResume.cs
+++ b/DisCatSharp/Net/Abstractions/Gateway/GatewayIdentifyResume.cs
@@ -1,104 +1,104 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net.Abstractions;
/// <summary>
/// Represents data for websocket identify payload.
/// </summary>
internal sealed class GatewayIdentify
{
/// <summary>
/// Gets or sets the discord client.
/// </summary>
[JsonIgnore]
public BaseDiscordClient Discord { get; set; }
/// <summary>
/// Gets or sets the token used to identify the client to Discord.
/// </summary>
[JsonProperty("token")]
public string Token { get; set; }
/// <summary>
/// Gets or sets the client's properties.
/// </summary>
[JsonProperty("properties")]
public ClientProperties ClientProperties =>
new() { Discord = this.Discord };
/// <summary>
/// Gets or sets whether to encrypt websocket traffic.
/// </summary>
[JsonProperty("compress")]
public bool Compress { get; set; }
/// <summary>
/// Gets or sets the member count at which the guild is to be considered large.
/// </summary>
[JsonProperty("large_threshold")]
public int LargeThreshold { get; set; }
/// <summary>
/// Gets or sets the shard info for this connection.
/// </summary>
[JsonProperty("shard")]
public ShardInfo ShardInfo { get; set; }
/// <summary>
/// Gets or sets the presence for this connection.
/// </summary>
[JsonProperty("presence", NullValueHandling = NullValueHandling.Ignore)]
public StatusUpdate Presence { get; set; }
/// <summary>
/// Gets or sets the intent flags for this connection.
/// </summary>
[JsonProperty("intents")]
public DiscordIntents Intents { get; set; }
}
/// <summary>
/// Represents data for websocket identify payload.
/// </summary>
internal sealed class GatewayResume
{
/// <summary>
/// Gets or sets the token used to identify the client to Discord.
/// </summary>
[JsonProperty("token")]
public string Token { get; set; }
/// <summary>
/// Gets or sets the session id used to resume last session.
/// </summary>
[JsonProperty("session_id")]
public string SessionId { get; set; }
/// <summary>
/// Gets or sets the last received sequence number.
/// </summary>
[JsonProperty("seq")]
public long SequenceNumber { get; set; }
}
diff --git a/DisCatSharp/Net/Abstractions/Gateway/GatewayInfo.cs b/DisCatSharp/Net/Abstractions/Gateway/GatewayInfo.cs
index dd061a7d8..823c1bcf4 100644
--- a/DisCatSharp/Net/Abstractions/Gateway/GatewayInfo.cs
+++ b/DisCatSharp/Net/Abstractions/Gateway/GatewayInfo.cs
@@ -1,49 +1,49 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net;
/// <summary>
/// Represents information used to identify with Discord.
/// </summary>
public sealed class GatewayInfo
{
/// <summary>
/// Gets the gateway URL for the WebSocket connection.
/// </summary>
[JsonProperty("url")]
public string Url { get; set; }
/// <summary>
/// Gets the recommended amount of shards.
/// </summary>
[JsonProperty("shards")]
public int ShardCount { get; internal set; }
/// <summary>
/// Gets the session start limit data.
/// </summary>
[JsonProperty("session_start_limit")]
public SessionBucket SessionBucket { get; internal set; }
}
diff --git a/DisCatSharp/Net/Abstractions/Gateway/GatewayOpCode.cs b/DisCatSharp/Net/Abstractions/Gateway/GatewayOpCode.cs
index a151afd47..f5fe54788 100644
--- a/DisCatSharp/Net/Abstractions/Gateway/GatewayOpCode.cs
+++ b/DisCatSharp/Net/Abstractions/Gateway/GatewayOpCode.cs
@@ -1,94 +1,94 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net.Abstractions;
/// <summary>
/// Specifies an OP code in a gateway payload.
/// </summary>
internal enum GatewayOpCode : int
{
/// <summary>
/// Used for dispatching events.
/// </summary>
Dispatch = 0,
/// <summary>
/// Used for pinging the gateway or client, to ensure the connection is still alive.
/// </summary>
Heartbeat = 1,
/// <summary>
/// Used for initial handshake with the gateway.
/// </summary>
Identify = 2,
/// <summary>
/// Used to update client status.
/// </summary>
StatusUpdate = 3,
/// <summary>
/// Used to update voice state, when joining, leaving, or moving between voice channels.
/// </summary>
VoiceStateUpdate = 4,
/// <summary>
/// Used for pinging the voice gateway or client, to ensure the connection is still alive.
/// </summary>
VoiceServerPing = 5,
/// <summary>
/// Used to resume a closed connection.
/// </summary>
Resume = 6,
/// <summary>
/// Used to notify the client that it has to reconnect.
/// </summary>
Reconnect = 7,
/// <summary>
/// Used to request guild members.
/// </summary>
RequestGuildMembers = 8,
/// <summary>
/// Used to notify the client about an invalidated session.
/// </summary>
InvalidSession = 9,
/// <summary>
/// Used by the gateway upon connecting.
/// </summary>
Hello = 10,
/// <summary>
/// Used to acknowledge a heartbeat.
/// </summary>
HeartbeatAck = 11,
/// <summary>
/// Used to request guild synchronization.
/// </summary>
GuildSync = 12
}
diff --git a/DisCatSharp/Net/Abstractions/Gateway/GatewayPayload.cs b/DisCatSharp/Net/Abstractions/Gateway/GatewayPayload.cs
index 636b3cc6d..bed1060e5 100644
--- a/DisCatSharp/Net/Abstractions/Gateway/GatewayPayload.cs
+++ b/DisCatSharp/Net/Abstractions/Gateway/GatewayPayload.cs
@@ -1,55 +1,55 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net.Abstractions;
/// <summary>
/// Represents a websocket payload exchanged between Discord and the client.
/// </summary>
internal sealed class GatewayPayload
{
/// <summary>
/// Gets or sets the OP code of the payload.
/// </summary>
[JsonProperty("op")]
public GatewayOpCode OpCode { get; set; }
/// <summary>
/// Gets or sets the data of the payload.
/// </summary>
[JsonProperty("d")]
public object Data { get; set; }
/// <summary>
/// Gets or sets the sequence number of the payload. Only present for OP 0.
/// </summary>
[JsonProperty("s", NullValueHandling = NullValueHandling.Ignore)]
public int? Sequence { get; set; }
/// <summary>
/// Gets or sets the event name of the payload. Only present for OP 0.
/// </summary>
[JsonProperty("t", NullValueHandling = NullValueHandling.Ignore)]
public string EventName { get; set; }
}
diff --git a/DisCatSharp/Net/Abstractions/Gateway/GatewayRequestGuildMembers.cs b/DisCatSharp/Net/Abstractions/Gateway/GatewayRequestGuildMembers.cs
index cddc7e1cf..3c1f4274e 100644
--- a/DisCatSharp/Net/Abstractions/Gateway/GatewayRequestGuildMembers.cs
+++ b/DisCatSharp/Net/Abstractions/Gateway/GatewayRequestGuildMembers.cs
@@ -1,80 +1,80 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Request guild members.
/// </summary>
internal sealed class GatewayRequestGuildMembers
{
/// <summary>
/// Gets the guild id.
/// </summary>
[JsonProperty("guild_id")]
public ulong GuildId { get; }
/// <summary>
/// Gets the query.
/// </summary>
[JsonProperty("query", NullValueHandling = NullValueHandling.Ignore)]
public string Query { get; set; }
/// <summary>
/// Gets the limit.
/// </summary>
[JsonProperty("limit")]
public int Limit { get; set; }
/// <summary>
/// Gets whether presences should be returned.
/// </summary>
[JsonProperty("presences", NullValueHandling = NullValueHandling.Ignore)]
public bool? Presences { get; set; }
/// <summary>
/// Gets the user ids.
/// </summary>
[JsonProperty("user_ids", NullValueHandling = NullValueHandling.Ignore)]
public IEnumerable<ulong> UserIds { get; set; }
/// <summary>
/// Gets the nonce.
/// </summary>
[JsonProperty("nonce", NullValueHandling = NullValueHandling.Ignore)]
public string Nonce { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="GatewayRequestGuildMembers"/> class.
/// </summary>
/// <param name="guild">The guild.</param>
public GatewayRequestGuildMembers(DiscordGuild guild)
{
this.GuildId = guild.Id;
}
}
diff --git a/DisCatSharp/Net/Abstractions/IOAuth2Payload.cs b/DisCatSharp/Net/Abstractions/IOAuth2Payload.cs
index 37fd25469..b77f2f9d8 100644
--- a/DisCatSharp/Net/Abstractions/IOAuth2Payload.cs
+++ b/DisCatSharp/Net/Abstractions/IOAuth2Payload.cs
@@ -1,34 +1,34 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net.Abstractions;
/// <summary>
/// Represents a OAuth2 payload.
/// </summary>
internal interface IOAuth2Payload
{
/// <summary>
/// Gets or sets the access token.
/// </summary>
string AccessToken { get; set; }
}
diff --git a/DisCatSharp/Net/Abstractions/ReadyPayload.cs b/DisCatSharp/Net/Abstractions/ReadyPayload.cs
index 1821d3519..c6dd23645 100644
--- a/DisCatSharp/Net/Abstractions/ReadyPayload.cs
+++ b/DisCatSharp/Net/Abstractions/ReadyPayload.cs
@@ -1,71 +1,71 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents data for websocket ready event payload.
/// </summary>
internal class ReadyPayload
{
/// <summary>
/// Gets the gateway version the client is connected to.
/// </summary>
[JsonProperty("v")]
public int GatewayVersion { get; private set; }
/// <summary>
/// Gets the current user.
/// </summary>
[JsonProperty("user")]
public TransportUser CurrentUser { get; private set; }
/// <summary>
/// Gets the guilds available for this shard.
/// </summary>
[JsonProperty("guilds")]
public IReadOnlyList<DiscordGuild> Guilds { get; private set; }
/// <summary>
/// Gets the current session's ID.
/// </summary>
[JsonProperty("session_id")]
public string SessionId { get; private set; }
/// <summary>
/// The gateway url for resuming connections
/// </summary>
[JsonProperty("resume_gateway_url")]
public string ResumeGatewayUrl { get; set; }
/// <summary>
/// Gets debug data sent by Discord. This contains a list of servers to which the client is connected.
/// </summary>
[JsonProperty("_trace")]
public IReadOnlyList<string> Trace { get; private set; }
}
diff --git a/DisCatSharp/Net/Abstractions/Rest/RestApplicationCommandPayloads.cs b/DisCatSharp/Net/Abstractions/Rest/RestApplicationCommandPayloads.cs
index c60a596f2..ca26a95e0 100644
--- a/DisCatSharp/Net/Abstractions/Rest/RestApplicationCommandPayloads.cs
+++ b/DisCatSharp/Net/Abstractions/Rest/RestApplicationCommandPayloads.cs
@@ -1,282 +1,282 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a application command create payload.
/// </summary>
internal class RestApplicationCommandCreatePayload
{
/// <summary>
/// Gets the type.
/// </summary>
[JsonProperty("type")]
public ApplicationCommandType Type { get; set; }
/// <summary>
/// Gets the name.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Gets the name localizations.
/// </summary>
[JsonProperty("name_localizations", NullValueHandling = NullValueHandling.Ignore)]
public Optional<Dictionary<string, string>> NameLocalizations { get; set; }
/// <summary>
/// Gets the description.
/// </summary>
[JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)]
public string Description { get; set; }
/// <summary>
/// Gets the description localizations.
/// </summary>
[JsonProperty("description_localizations", NullValueHandling = NullValueHandling.Ignore)]
public Optional<Dictionary<string, string>> DescriptionLocalizations { get; set; }
/// <summary>
/// Gets the options.
/// </summary>
[JsonProperty("options", NullValueHandling = NullValueHandling.Ignore)]
public IEnumerable<DiscordApplicationCommandOption> Options { get; set; }
/// <summary>
/// Whether the command is allowed for everyone.
/// </summary>
[JsonProperty("default_permission", NullValueHandling = NullValueHandling.Include)]
public bool? DefaultPermission { get; set; } = null;
/// <summary>
/// The command needed permissions.
/// </summary>
[JsonProperty("default_member_permissions", NullValueHandling = NullValueHandling.Include)]
public Permissions? DefaultMemberPermission { get; set; }
/// <summary>
/// Whether the command is allowed for dms.
/// </summary>
[JsonProperty("dm_permission", NullValueHandling = NullValueHandling.Include)]
public bool? DmPermission { get; set; }
/// <summary>
/// Whether the command is marked as NSFW.
/// </summary>
[JsonProperty("nsfw", NullValueHandling = NullValueHandling.Ignore)]
public bool Nsfw { get; set; }
}
/// <summary>
/// Represents a application command edit payload.
/// </summary>
internal class RestApplicationCommandEditPayload
{
/// <summary>
/// Gets the name.
/// </summary>
[JsonProperty("name")]
public Optional<string> Name { get; set; }
/// <summary>
/// Gets the name localizations.
/// </summary>
[JsonProperty("name_localizations")]
public Optional<Dictionary<string, string>> NameLocalizations { get; set; }
/// <summary>
/// Gets the description.
/// </summary>
[JsonProperty("description")]
public Optional<string> Description { get; set; }
/// <summary>
/// Gets the description localizations.
/// </summary>
[JsonProperty("description_localizations")]
public Optional<Dictionary<string, string>> DescriptionLocalizations { get; set; }
/// <summary>
/// Gets the options.
/// </summary>
[JsonProperty("options", NullValueHandling = NullValueHandling.Ignore)]
public Optional<List<DiscordApplicationCommandOption>> Options { get; set; }
/// <summary>
/// The command needed permissions.
/// </summary>
[JsonProperty("default_member_permissions", NullValueHandling = NullValueHandling.Include)]
public Optional<Permissions?> DefaultMemberPermission { get; set; }
/// <summary>
/// Whether the command is allowed for dms.
/// </summary>
[JsonProperty("dm_permission", NullValueHandling = NullValueHandling.Include)]
public Optional<bool> DmPermission { get; set; }
/// <summary>
/// Whether the command is marked as NSFW.
/// </summary>
[JsonProperty("nsfw", NullValueHandling = NullValueHandling.Ignore)]
public Optional<bool> Nsfw { get; set; }
}
/// <summary>
/// Represents a interaction response payload.
/// </summary>
internal class RestInteractionResponsePayload
{
/// <summary>
/// Gets the type.
/// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
public InteractionResponseType Type { get; set; }
/// <summary>
/// Gets the data.
/// </summary>
[JsonProperty("data", NullValueHandling = NullValueHandling.Ignore)]
public DiscordInteractionApplicationCommandCallbackData Data { get; set; }
/// <summary>
/// Gets the attachments.
/// </summary>
[JsonProperty("attachments", NullValueHandling = NullValueHandling.Ignore)]
public List<DiscordAttachment> Attachments { get; set; }
}
/// <summary>
/// Represents a interaction response payload.
/// </summary>
internal class RestInteractionModalResponsePayload
{
/// <summary>
/// Gets the type.
/// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
public InteractionResponseType Type { get; set; }
/// <summary>
/// Gets the data.
/// </summary>
[JsonProperty("data", NullValueHandling = NullValueHandling.Ignore)]
public DiscordInteractionApplicationCommandModalCallbackData Data { get; set; }
}
/// <summary>
/// Represents a followup message create payload.
/// </summary>
internal class RestFollowupMessageCreatePayload
{
/// <summary>
/// Gets the content.
/// </summary>
[JsonProperty("content", NullValueHandling = NullValueHandling.Ignore)]
public string Content { get; set; }
/// <summary>
/// Get whether the message is tts.
/// </summary>
[JsonProperty("tts", NullValueHandling = NullValueHandling.Ignore)]
public bool? IsTts { get; set; }
/// <summary>
/// Gets the embeds.
/// </summary>
[JsonProperty("embeds", NullValueHandling = NullValueHandling.Ignore)]
public IEnumerable<DiscordEmbed> Embeds { get; set; }
/// <summary>
/// Gets the mentions.
/// </summary>
[JsonProperty("allowed_mentions", NullValueHandling = NullValueHandling.Ignore)]
public DiscordMentions Mentions { get; set; }
/// <summary>
/// Gets the flags.
/// </summary>
[JsonProperty("flags", NullValueHandling = NullValueHandling.Ignore)]
public int? Flags { get; set; }
/// <summary>
/// Gets the components.
/// </summary>
[JsonProperty("components", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyCollection<DiscordActionRowComponent> Components { get; set; }
/// <summary>
/// Gets attachments.
/// </summary>
[JsonProperty("attachments", NullValueHandling = NullValueHandling.Ignore)]
public List<DiscordAttachment> Attachments { get; set; }
}
/// <summary>
/// Represents a role connection metadata payload.
/// </summary>
internal class RestApplicationRoleConnectionMetadataPayload
{
/// <summary>
/// Gets the metadata type.
/// </summary>
[JsonProperty("type")]
public ApplicationRoleConnectionMetadataType Type { get; set; }
/// <summary>
/// Gets the metadata key.
/// </summary>
[JsonProperty("key")]
public string Key { get; set; }
/// <summary>
/// Gets the metadata name.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Gets the metadata description.
/// </summary>
[JsonProperty("description")]
public string Description { get; set; }
/// <summary>
/// Gets the metadata name translations.
/// </summary>
[JsonProperty("name_localizations", NullValueHandling = NullValueHandling.Ignore)]
public Dictionary<string, string> NameLocalizations { get; set; }
/// <summary>
/// Gets the metadata description localizations.
/// </summary>
[JsonProperty("description_localizations", NullValueHandling = NullValueHandling.Ignore)]
public Dictionary<string, string> DescriptionLocalizations { get; set; }
}
diff --git a/DisCatSharp/Net/Abstractions/Rest/RestAutomodRuleModifyPayload.cs b/DisCatSharp/Net/Abstractions/Rest/RestAutomodRuleModifyPayload.cs
index bf397c42f..df6916709 100644
--- a/DisCatSharp/Net/Abstractions/Rest/RestAutomodRuleModifyPayload.cs
+++ b/DisCatSharp/Net/Abstractions/Rest/RestAutomodRuleModifyPayload.cs
@@ -1,58 +1,58 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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
{
internal class RestAutomodRuleModifyPayload
{
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public Optional<string> Name { get; set; }
[JsonProperty("event_type", NullValueHandling = NullValueHandling.Ignore)]
public Optional<AutomodEventType> EventType { get; set; }
[JsonProperty("trigger_type", NullValueHandling = NullValueHandling.Ignore)]
public Optional<AutomodTriggerType> TriggerType { get; set; }
[JsonProperty("trigger_metadata", NullValueHandling = NullValueHandling.Ignore)]
public Optional<AutomodTriggerMetadata> TriggerMetadata { get; set; }
[JsonProperty("actions", NullValueHandling = NullValueHandling.Ignore)]
public Optional<IEnumerable<AutomodAction>> Actions { get; set; }
[JsonProperty("enabled", NullValueHandling = NullValueHandling.Ignore)]
public Optional<bool> Enabled { get; set; }
[JsonProperty("exempt_roles", NullValueHandling = NullValueHandling.Ignore)]
public Optional<IEnumerable<ulong>> ExemptRoles { get; set; }
[JsonProperty("exempt_channels", NullValueHandling = NullValueHandling.Ignore)]
public Optional<IEnumerable<ulong>> ExemptChannels { get; set; }
}
}
diff --git a/DisCatSharp/Net/Abstractions/Rest/RestChannelPayloads.cs b/DisCatSharp/Net/Abstractions/Rest/RestChannelPayloads.cs
index 5616a35c7..874b98162 100644
--- a/DisCatSharp/Net/Abstractions/Rest/RestChannelPayloads.cs
+++ b/DisCatSharp/Net/Abstractions/Rest/RestChannelPayloads.cs
@@ -1,582 +1,582 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a channel create payload.
/// </summary>
internal sealed class RestChannelCreatePayload
{
/// <summary>
/// Gets or sets the name.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Gets or sets the type.
/// </summary>
[JsonProperty("type")]
public ChannelType Type { get; set; }
/// <summary>
/// Gets or sets the parent.
/// </summary>
[JsonProperty("parent_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? Parent { get; set; }
/// <summary>
/// Gets or sets the topic.
/// </summary>
[JsonProperty("topic")]
public Optional<string> Topic { get; set; }
/// <summary>
/// Gets or sets the template.
/// </summary>
[JsonProperty("template")]
public Optional<string> Template { get; set; }
/// <summary>
/// Gets or sets the bitrate.
/// </summary>
[JsonProperty("bitrate", NullValueHandling = NullValueHandling.Ignore)]
public int? Bitrate { get; set; }
/// <summary>
/// Gets or sets the user limit.
/// </summary>
[JsonProperty("user_limit", NullValueHandling = NullValueHandling.Ignore)]
public int? UserLimit { get; set; }
/// <summary>
/// Gets or sets the permission overwrites.
/// </summary>
[JsonProperty("permission_overwrites", NullValueHandling = NullValueHandling.Ignore)]
public IEnumerable<DiscordRestOverwrite> PermissionOverwrites { get; set; }
/// <summary>
/// Gets or sets a value indicating whether nsfw.
/// </summary>
[JsonProperty("nsfw", NullValueHandling = NullValueHandling.Ignore)]
public bool? Nsfw { get; set; }
/// <summary>
/// Gets or sets the per user rate limit.
/// </summary>
[JsonProperty("rate_limit_per_user")]
public Optional<int?> PerUserRateLimit { get; set; }
[JsonProperty("default_thread_rate_limit_per_user", NullValueHandling = NullValueHandling.Ignore)]
public Optional<int?> PostCreateUserRateLimit { get; internal set; }
/// <summary>
/// Gets or sets the quality mode.
/// </summary>
[JsonProperty("video_quality_mode", NullValueHandling = NullValueHandling.Ignore)]
public VideoQualityMode? QualityMode { get; set; }
/// <summary>
/// Gets or sets the default auto archive duration.
/// </summary>
[JsonProperty("default_auto_archive_duration", NullValueHandling = NullValueHandling.Ignore)]
public Optional<ThreadAutoArchiveDuration?> DefaultAutoArchiveDuration { get; set; }
/// <summary>
/// Gets the default reaction emoji for forum posts.
/// </summary>
[JsonProperty("default_reaction_emoji", NullValueHandling = NullValueHandling.Ignore)]
public Optional<ForumReactionEmoji> DefaultReactionEmoji { get; internal set; }
/// <summary>
/// Gets the default forum post sort order
/// </summary>
[JsonProperty("default_sort_order", NullValueHandling = NullValueHandling.Ignore)]
public Optional<ForumPostSortOrder> DefaultSortOrder { get; internal set; }
/// <summary>
/// Gets or sets the channel flags.
/// </summary>
[JsonProperty("flags", NullValueHandling = NullValueHandling.Include)]
public Optional<ChannelFlags?> Flags { internal get; set; }
}
/// <summary>
/// Represents a channel modify payload.
/// </summary>
internal sealed class RestChannelModifyPayload
{
/// <summary>
/// Gets or sets the name.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; set; }
/// <summary>
/// Gets or sets the type.
/// </summary>
[JsonProperty("type")]
public Optional<ChannelType> Type { get; set; }
/// <summary>
/// Gets or sets the position.
/// </summary>
[JsonProperty("position", NullValueHandling = NullValueHandling.Ignore)]
public int? Position { get; set; }
/// <summary>
/// Gets or sets the topic.
/// </summary>
[JsonProperty("topic")]
public Optional<string> Topic { get; set; }
/// <summary>
/// Gets or sets the template.
/// </summary>
[JsonProperty("template")]
public Optional<string> Template { get; set; }
/// <summary>
/// Gets or sets a value indicating whether nsfw.
/// </summary>
[JsonProperty("nsfw", NullValueHandling = NullValueHandling.Ignore)]
public bool? Nsfw { get; set; }
/// <summary>
/// Gets or sets the parent.
/// </summary>
[JsonProperty("parent_id")]
public Optional<ulong?> Parent { get; set; }
/// <summary>
/// Gets or sets the bitrate.
/// </summary>
[JsonProperty("bitrate", NullValueHandling = NullValueHandling.Ignore)]
public int? Bitrate { get; set; }
/// <summary>
/// Gets or sets the user limit.
/// </summary>
[JsonProperty("user_limit", NullValueHandling = NullValueHandling.Ignore)]
public Optional<int?> UserLimit { get; set; }
/// <summary>
/// Gets or sets the per user rate limit.
/// </summary>
[JsonProperty("rate_limit_per_user")]
public Optional<int?> PerUserRateLimit { get; set; }
[JsonProperty("default_thread_rate_limit_per_user", NullValueHandling = NullValueHandling.Ignore)]
public Optional<int?> PostCreateUserRateLimit { get; internal set; }
/// <summary>
/// Gets or sets the rtc region.
/// </summary>
[JsonProperty("rtc_region")]
public Optional<string> RtcRegion { get; set; }
/// <summary>
/// Gets or sets the quality mode.
/// </summary>
[JsonProperty("video_quality_mode", NullValueHandling = NullValueHandling.Ignore)]
public VideoQualityMode? QualityMode { get; set; }
/// <summary>
/// Gets or sets the default auto archive duration.
/// </summary>
[JsonProperty("default_auto_archive_duration", NullValueHandling = NullValueHandling.Ignore)]
public Optional<ThreadAutoArchiveDuration?> DefaultAutoArchiveDuration { get; set; }
/// <summary>
/// Gets or sets the permission overwrites.
/// </summary>
[JsonProperty("permission_overwrites", NullValueHandling = NullValueHandling.Ignore)]
public IEnumerable<DiscordRestOverwrite> PermissionOverwrites { get; set; }
/// <summary>
/// Gets the default reaction emoji for forum posts.
/// </summary>
[JsonProperty("default_reaction_emoji", NullValueHandling = NullValueHandling.Ignore)]
public Optional<ForumReactionEmoji> DefaultReactionEmoji { get; internal set; }
/// <summary>
/// Gets the default forum post sort order
/// </summary>
[JsonProperty("default_sort_order", NullValueHandling = NullValueHandling.Ignore)]
public Optional<ForumPostSortOrder?> DefaultSortOrder { get; internal set; }
/// <summary>
/// Gets or sets the channel flags.
/// </summary>
[JsonProperty("flags", NullValueHandling = NullValueHandling.Include)]
public Optional<ChannelFlags?> Flags { internal get; set; }
/// <summary>
/// Gets or sets the available tags.
/// </summary>
[JsonProperty("available_tags", NullValueHandling = NullValueHandling.Ignore)]
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public Optional<List<ForumPostTag>?> AvailableTags { internal get; set; }
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
}
/// <summary>
/// Represents a channel message edit payload.
/// </summary>
internal class RestChannelMessageEditPayload
{
/// <summary>
/// Gets or sets the content.
/// </summary>
[JsonProperty("content", NullValueHandling = NullValueHandling.Include)]
public string Content { get; set; }
/// <summary>
/// Gets or sets a value indicating whether has content.
/// </summary>
[JsonIgnore]
public bool HasContent { get; set; }
/// <summary>
/// Gets or sets the embeds.
/// </summary>
[JsonProperty("embeds", NullValueHandling = NullValueHandling.Ignore)]
public IEnumerable<DiscordEmbed> Embeds { get; set; }
/// <summary>
/// Gets or sets the mentions.
/// </summary>
[JsonProperty("allowed_mentions", NullValueHandling = NullValueHandling.Ignore)]
public DiscordMentions Mentions { get; set; }
/// <summary>
/// Gets or sets the attachments.
/// </summary>
[JsonProperty("attachments", NullValueHandling = NullValueHandling.Ignore)]
public IEnumerable<DiscordAttachment> Attachments { get; set; }
/// <summary>
/// Gets or sets the flags.
/// </summary>
[JsonProperty("flags", NullValueHandling = NullValueHandling.Ignore)]
public MessageFlags? Flags { get; set; }
/// <summary>
/// Gets or sets the components.
/// </summary>
[JsonProperty("components", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyCollection<DiscordActionRowComponent> Components { get; set; }
/// <summary>
/// Gets or sets a value indicating whether has embed.
/// </summary>
[JsonIgnore]
public bool HasEmbed { get; set; }
/// <summary>
/// Should serialize the content.
/// </summary>
public bool ShouldSerializeContent()
=> this.HasContent;
/// <summary>
/// Should serialize the embed.
/// </summary>
public bool ShouldSerializeEmbed()
=> this.HasEmbed;
}
/// <summary>
/// Represents a channel message create payload.
/// </summary>
internal sealed class RestChannelMessageCreatePayload : RestChannelMessageEditPayload
{
/// <summary>
/// Gets or sets a value indicating whether t t is s.
/// </summary>
[JsonProperty("tts", NullValueHandling = NullValueHandling.Ignore)]
public bool? IsTts { get; set; }
/// <summary>
/// Gets or sets the stickers ids.
/// </summary>
[JsonProperty("sticker_ids", NullValueHandling = NullValueHandling.Ignore)]
public IEnumerable<ulong> StickersIds { get; set; }
/// <summary>
/// Gets or sets the message reference.
/// </summary>
[JsonProperty("message_reference", NullValueHandling = NullValueHandling.Ignore)]
public InternalDiscordMessageReference? MessageReference { get; set; }
}
/// <summary>
/// Represents a channel message create multipart payload.
/// </summary>
internal sealed class RestChannelMessageCreateMultipartPayload
{
/// <summary>
/// Gets or sets the content.
/// </summary>
[JsonProperty("content", NullValueHandling = NullValueHandling.Ignore)]
public string Content { get; set; }
/// <summary>
/// Gets or sets a value indicating whether t t is s.
/// </summary>
[JsonProperty("tts", NullValueHandling = NullValueHandling.Ignore)]
public bool? IsTts { get; set; }
/// <summary>
/// Gets or sets the embeds.
/// </summary>
[JsonProperty("embeds", NullValueHandling = NullValueHandling.Ignore)]
public IEnumerable<DiscordEmbed> Embeds { get; set; }
/// <summary>
/// Gets or sets the mentions.
/// </summary>
[JsonProperty("allowed_mentions", NullValueHandling = NullValueHandling.Ignore)]
public DiscordMentions Mentions { get; set; }
/// <summary>
/// Gets or sets the message reference.
/// </summary>
[JsonProperty("message_reference", NullValueHandling = NullValueHandling.Ignore)]
public InternalDiscordMessageReference? MessageReference { get; set; }
}
/// <summary>
/// Represents a channel message bulk delete payload.
/// </summary>
internal sealed class RestChannelMessageBulkDeletePayload
{
/// <summary>
/// Gets or sets the messages.
/// </summary>
[JsonProperty("messages", NullValueHandling = NullValueHandling.Ignore)]
public IEnumerable<ulong> Messages { get; set; }
}
/// <summary>
/// Represents a channel invite create payload.
/// </summary>
internal sealed class RestChannelInviteCreatePayload
{
/// <summary>
/// Gets or sets the max age.
/// </summary>
[JsonProperty("max_age", NullValueHandling = NullValueHandling.Ignore)]
public int MaxAge { get; set; }
/// <summary>
/// Gets or sets the max uses.
/// </summary>
[JsonProperty("max_uses", NullValueHandling = NullValueHandling.Ignore)]
public int MaxUses { get; set; }
/// <summary>
/// Gets or sets the target type.
/// </summary>
[JsonProperty("target_type", NullValueHandling = NullValueHandling.Ignore)]
public TargetType? TargetType { get; set; }
/// <summary>
/// Gets or sets the target application.
/// </summary>
[JsonProperty("target_application_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? TargetApplicationId { get; set; }
/// <summary>
/// Gets or sets the target user id.
/// </summary>
[JsonProperty("target_user_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? TargetUserId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether temporary.
/// </summary>
[JsonProperty("temporary", NullValueHandling = NullValueHandling.Ignore)]
public bool Temporary { get; set; }
/// <summary>
/// Gets or sets a value indicating whether unique.
/// </summary>
[JsonProperty("unique", NullValueHandling = NullValueHandling.Ignore)]
public bool Unique { get; set; }
}
/// <summary>
/// Represents a channel permission edit payload.
/// </summary>
internal sealed class RestChannelPermissionEditPayload
{
/// <summary>
/// Gets or sets the allow.
/// </summary>
[JsonProperty("allow", NullValueHandling = NullValueHandling.Ignore)]
public Permissions Allow { get; set; }
/// <summary>
/// Gets or sets the deny.
/// </summary>
[JsonProperty("deny", NullValueHandling = NullValueHandling.Ignore)]
public Permissions Deny { get; set; }
/// <summary>
/// Gets or sets the type.
/// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
public string Type { get; set; }
}
/// <summary>
/// Represents a channel group dm recipient add payload.
/// </summary>
internal sealed class RestChannelGroupDmRecipientAddPayload : IOAuth2Payload
{
/// <summary>
/// Gets or sets the access token.
/// </summary>
[JsonProperty("access_token")]
public string AccessToken { get; set; }
/// <summary>
/// Gets or sets the nickname.
/// </summary>
[JsonProperty("nick", NullValueHandling = NullValueHandling.Ignore)]
public string Nickname { get; set; }
}
/// <summary>
/// The acknowledge payload.
/// </summary>
internal sealed class AcknowledgePayload
{
/// <summary>
/// Gets or sets the token.
/// </summary>
[JsonProperty("token", NullValueHandling = NullValueHandling.Include)]
public string Token { get; set; }
}
/// <summary>
/// Represents a thread channel create payload.
/// </summary>
internal sealed class RestThreadChannelCreatePayload
{
/// <summary>
/// Gets or sets the name.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("message", NullValueHandling = NullValueHandling.Ignore)]
public RestChannelMessageCreatePayload Message { get; set; }
/// <summary>
/// Gets or sets the auto archive duration.
/// </summary>
[JsonProperty("auto_archive_duration", NullValueHandling = NullValueHandling.Ignore)]
public Optional<ThreadAutoArchiveDuration?> AutoArchiveDuration { get; set; }
/// <summary>
/// Gets or sets the rate limit per user.
/// </summary>
[JsonProperty("rate_limit_per_user")]
public int? PerUserRateLimit { get; set; }
/// <summary>
/// Gets or sets the thread type.
/// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
public ChannelType? Type { get; set; }
/// <summary>
/// Gets or sets the applied tags.
/// </summary>
[JsonProperty("applied_tags", NullValueHandling = NullValueHandling.Ignore)]
public Optional<IEnumerable<ulong>> AppliedTags { internal get; set; }
}
/// <summary>
/// Represents a thread channel modify payload.
/// </summary>
internal sealed class RestThreadChannelModifyPayload
{
/// <summary>
/// Gets or sets the name.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; set; }
/// <summary>
/// Gets or sets the archived.
/// </summary>
[JsonProperty("archived", NullValueHandling = NullValueHandling.Ignore)]
public Optional<bool?> Archived { get; set; }
/// <summary>
/// Gets or sets the auto archive duration.
/// </summary>
[JsonProperty("auto_archive_duration", NullValueHandling = NullValueHandling.Ignore)]
public Optional<ThreadAutoArchiveDuration?> AutoArchiveDuration { get; set; }
/// <summary>
/// Gets or sets the locked.
/// </summary>
[JsonProperty("locked", NullValueHandling = NullValueHandling.Ignore)]
public Optional<bool?> Locked { get; set; }
/// <summary>
/// Gets or sets the per user rate limit.
/// </summary>
[JsonProperty("rate_limit_per_user", NullValueHandling = NullValueHandling.Ignore)]
public Optional<int?> PerUserRateLimit { get; set; }
/// <summary>
/// Gets or sets the thread's invitable state.
/// </summary>
[JsonProperty("invitable", NullValueHandling = NullValueHandling.Ignore)]
public Optional<bool?> Invitable { internal get; set; }
/// <summary>
/// Gets or sets the applied tags.
/// </summary>
[JsonProperty("applied_tags", NullValueHandling = NullValueHandling.Ignore)]
public Optional<IEnumerable<ulong>> AppliedTags { internal get; set; }
/// <summary>
/// Gets or sets the channel flags.
/// </summary>
[JsonProperty("flags", NullValueHandling = NullValueHandling.Include)]
public Optional<ChannelFlags?> Flags { internal get; set; }
}
diff --git a/DisCatSharp/Net/Abstractions/Rest/RestForumPostTagPayloads.cs b/DisCatSharp/Net/Abstractions/Rest/RestForumPostTagPayloads.cs
index c70b6811e..98812ad34 100644
--- a/DisCatSharp/Net/Abstractions/Rest/RestForumPostTagPayloads.cs
+++ b/DisCatSharp/Net/Abstractions/Rest/RestForumPostTagPayloads.cs
@@ -1,57 +1,57 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents the forum tag payload.
/// </summary>
public class RestForumPostTagPayloads
{
/// <summary>
/// Sets the tags's new name.
/// </summary>
[JsonProperty("name")]
public string Name { internal get; set; }
/// <summary>
/// Sets the tags's new emoji.
/// </summary>
[JsonProperty("emoji_id", NullValueHandling = NullValueHandling.Include)]
public Optional<ulong?> Emoji { internal get; set; }
/// <summary>
/// Sets whether the tag should be mod only.
/// </summary>
[JsonProperty("moderated", NullValueHandling = NullValueHandling.Ignore)]
public bool Moderated { internal get; set; }
/// <summary>
/// Gets the unicode emoji of the forum post tag.
/// </summary>
[JsonProperty("emoji_name", NullValueHandling = NullValueHandling.Include)]
public Optional<string> UnicodeEmojiString { internal get; set; }
}
diff --git a/DisCatSharp/Net/Abstractions/Rest/RestGuildPayloads.cs b/DisCatSharp/Net/Abstractions/Rest/RestGuildPayloads.cs
index d0c62911d..43d647a79 100644
--- a/DisCatSharp/Net/Abstractions/Rest/RestGuildPayloads.cs
+++ b/DisCatSharp/Net/Abstractions/Rest/RestGuildPayloads.cs
@@ -1,772 +1,772 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using DisCatSharp.Entities;
using DisCatSharp.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Net.Abstractions;
/// <summary>
/// The reason action.
/// </summary>
internal interface IReasonAction
{
/// <summary>
/// Gets or sets the reason.
/// </summary>
string Reason { get; set; }
//[JsonProperty("reason", NullValueHandling = NullValueHandling.Ignore)]
//public string Reason { get; set; }
}
/// <summary>
/// Represents a guild create payload.
/// </summary>
internal class RestGuildCreatePayload
{
/// <summary>
/// Gets or sets the name.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; set; }
/// <summary>
/// Gets or sets the region id.
/// </summary>
[JsonProperty("region", NullValueHandling = NullValueHandling.Ignore)]
public string RegionId { get; set; }
/// <summary>
/// Gets or sets the icon base64.
/// </summary>
[JsonProperty("icon", NullValueHandling = NullValueHandling.Include)]
public Optional<string> IconBase64 { get; set; }
/// <summary>
/// Gets or sets the verification level.
/// </summary>
[JsonProperty("verification_level", NullValueHandling = NullValueHandling.Ignore)]
public VerificationLevel? VerificationLevel { get; set; }
/// <summary>
/// Gets or sets the default message notifications.
/// </summary>
[JsonProperty("default_message_notifications", NullValueHandling = NullValueHandling.Ignore)]
public DefaultMessageNotifications? DefaultMessageNotifications { get; set; }
/// <summary>
/// Gets or sets the system channel flags.
/// </summary>
[JsonProperty("system_channel_flags", NullValueHandling = NullValueHandling.Ignore)]
public SystemChannelFlags? SystemChannelFlags { get; set; }
/// <summary>
/// Gets or sets the roles.
/// </summary>
[JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)]
public IEnumerable<DiscordRole> Roles { get; set; }
/// <summary>
/// Gets or sets the channels.
/// </summary>
[JsonProperty("channels", NullValueHandling = NullValueHandling.Ignore)]
public IEnumerable<RestChannelCreatePayload> Channels { get; set; }
}
/// <summary>
/// Represents a guild create from template payload.
/// </summary>
internal sealed class RestGuildCreateFromTemplatePayload
{
/// <summary>
/// Gets or sets the name.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; set; }
/// <summary>
/// Gets or sets the icon base64.
/// </summary>
[JsonProperty("icon", NullValueHandling = NullValueHandling.Include)]
public Optional<string> IconBase64 { get; set; }
}
/// <summary>
/// Represents a guild modify payload.
/// </summary>
internal sealed class RestGuildModifyPayload
{
/// <summary>
/// Gets or sets the name.
/// </summary>
[JsonProperty("name")]
public Optional<string> Name { get; set; }
/// <summary>
/// Gets or sets the icon base64.
/// </summary>
[JsonProperty("icon")]
public Optional<string> IconBase64 { get; set; }
/// <summary>
/// Gets or sets the verification level.
/// </summary>
[JsonProperty("verification_level")]
public Optional<VerificationLevel> VerificationLevel { get; set; }
/// <summary>
/// Gets or sets the default message notifications.
/// </summary>
[JsonProperty("default_message_notifications")]
public Optional<DefaultMessageNotifications> DefaultMessageNotifications { get; set; }
/// <summary>
/// Gets or sets the owner id.
/// </summary>
[JsonProperty("owner_id")]
public Optional<ulong> OwnerId { get; set; }
/// <summary>
/// Gets or sets the splash base64.
/// </summary>
[JsonProperty("splash")]
public Optional<string> SplashBase64 { get; set; }
/// <summary>
/// Gets or sets the banner base64.
/// </summary>
[JsonProperty("banner")]
public Optional<string> BannerBase64 { get; set; }
/// <summary>
/// Gets or sets the discovery splash base64.
/// </summary>
[JsonProperty("discovery_splash")]
public Optional<string> DiscoverySplashBase64 { get; set; }
/// <summary>
/// Gets or sets the afk channel id.
/// </summary>
[JsonProperty("afk_channel_id")]
public Optional<ulong?> AfkChannelId { get; set; }
/// <summary>
/// Gets or sets the afk timeout.
/// </summary>
[JsonProperty("afk_timeout")]
public Optional<int> AfkTimeout { get; set; }
/// <summary>
/// Gets or sets the mfa level.
/// </summary>
[JsonProperty("mfa_level")]
public Optional<MfaLevel> MfaLevel { get; set; }
/// <summary>
/// Gets or sets the explicit content filter.
/// </summary>
[JsonProperty("explicit_content_filter")]
public Optional<ExplicitContentFilter> ExplicitContentFilter { get; set; }
/// <summary>
/// Gets or sets the system channel id.
/// </summary>
[JsonProperty("system_channel_id", NullValueHandling = NullValueHandling.Include)]
public Optional<ulong?> SystemChannelId { get; set; }
/// <summary>
/// Gets or sets the safety alerts channel id.
/// </summary>
[JsonProperty("safety_alerts_channel_id", NullValueHandling = NullValueHandling.Include)]
internal Optional<ulong?> SafetyAlertsChannelId { get; set; }
/// <summary>
/// Gets or sets the system channel flags.
/// </summary>
[JsonProperty("system_channel_flags", NullValueHandling = NullValueHandling.Ignore)]
public Optional<SystemChannelFlags> SystemChannelFlags { get; set; }
/// <summary>
/// Gets or sets the rules channel id.
/// </summary>
[JsonProperty("rules_channel_id")]
public Optional<ulong?> RulesChannelId { get; set; }
/// <summary>
/// Gets or sets the public updates channel id.
/// </summary>
[JsonProperty("public_updates_channel_id")]
public Optional<ulong?> PublicUpdatesChannelId { get; set; }
/// <summary>
/// Gets or sets the preferred locale.
/// </summary>
[JsonProperty("preferred_locale")]
public Optional<string> PreferredLocale { get; set; }
/// <summary>
/// Gets or sets the description.
/// </summary>
[JsonProperty("description", NullValueHandling = NullValueHandling.Include)]
public Optional<string> Description { get; set; }
/// <summary>
/// Gets or sets whether the premium progress bar should be enabled.
/// </summary>
[JsonProperty("premium_progress_bar_enabled", NullValueHandling = NullValueHandling.Ignore)]
public Optional<bool> PremiumProgressBarEnabled { get; set; }
}
/// <summary>
/// Represents a guild mfa level modify payload.
/// </summary>
internal sealed class RestGuildMfaLevelModifyPayload
{
/// <summary>
/// Gets or sets the mfa level.
/// </summary>
[JsonProperty("level")]
public MfaLevel Level { get; set; }
}
/// <summary>
/// Represents a guild community modify payload.
/// </summary>
internal sealed class RestGuildFeatureModifyPayload
{
/// <summary>
/// Gets or sets the features.
/// </summary>
[JsonProperty("features", NullValueHandling = NullValueHandling.Ignore)]
public List<string> Features { get; set; }
}
internal sealed class RestGuildSafetyModifyPayload
{
/// <summary>
/// Gets or sets the safety alerts channel id.
/// </summary>
[JsonProperty("safety_alerts_channel_id", NullValueHandling = NullValueHandling.Include)]
internal Optional<ulong?> SafetyAlertsChannelId { get; set; }
/// <summary>
/// Gets or sets the features.
/// </summary>
[JsonProperty("features", NullValueHandling = NullValueHandling.Ignore)]
public List<string> Features { get; set; }
}
/// <summary>
/// Represents a guild community modify payload.
/// </summary>
internal sealed class RestGuildCommunityModifyPayload
{
/// <summary>
/// Gets or sets the verification level.
/// </summary>
[JsonProperty("verification_level", NullValueHandling = NullValueHandling.Ignore)]
public Optional<VerificationLevel> VerificationLevel { get; set; }
/// <summary>
/// Gets or sets the default message notifications.
/// </summary>
[JsonProperty("default_message_notifications", NullValueHandling = NullValueHandling.Ignore)]
public Optional<DefaultMessageNotifications> DefaultMessageNotifications { get; set; }
/// <summary>
/// Gets or sets the explicit content filter.
/// </summary>
[JsonProperty("explicit_content_filter", NullValueHandling = NullValueHandling.Ignore)]
public Optional<ExplicitContentFilter> ExplicitContentFilter { get; set; }
/// <summary>
/// Gets or sets the rules channel id.
/// </summary>
[JsonProperty("rules_channel_id", NullValueHandling = NullValueHandling.Ignore)]
public Optional<ulong?> RulesChannelId { get; set; }
/// <summary>
/// Gets or sets the public updates channel id.
/// </summary>
[JsonProperty("public_updates_channel_id", NullValueHandling = NullValueHandling.Ignore)]
public Optional<ulong?> PublicUpdatesChannelId { get; set; }
/// <summary>
/// Gets or sets the preferred locale.
/// </summary>
[JsonProperty("preferred_locale")]
public Optional<string> PreferredLocale { get; set; }
/// <summary>
/// Gets or sets the description.
/// </summary>
[JsonProperty("description", NullValueHandling = NullValueHandling.Include)]
public Optional<string> Description { get; set; }
/// <summary>
/// Gets or sets the features.
/// </summary>
[JsonProperty("features", NullValueHandling = NullValueHandling.Ignore)]
public List<string> Features { get; set; }
}
/// <summary>
/// Represents a guild member add payload.
/// </summary>
internal sealed class RestGuildMemberAddPayload : IOAuth2Payload
{
/// <summary>
/// Gets or sets the access token.
/// </summary>
[JsonProperty("access_token")]
public string AccessToken { get; set; }
/// <summary>
/// Gets or sets the nickname.
/// </summary>
[JsonProperty("nick", NullValueHandling = NullValueHandling.Ignore)]
public string Nickname { get; set; }
/// <summary>
/// Gets or sets the roles.
/// </summary>
[JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)]
public IEnumerable<DiscordRole> Roles { get; set; }
/// <summary>
/// Gets or sets a value indicating whether mute.
/// </summary>
[JsonProperty("mute", NullValueHandling = NullValueHandling.Ignore)]
public bool? Mute { get; set; }
/// <summary>
/// Gets or sets a value indicating whether deaf.
/// </summary>
[JsonProperty("deaf", NullValueHandling = NullValueHandling.Ignore)]
public bool? Deaf { get; set; }
}
/// <summary>
/// Represents a guild channel reorder payload.
/// </summary>
internal sealed class RestGuildChannelReorderPayload
{
/// <summary>
/// Gets or sets the channel id.
/// </summary>
[JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)]
public ulong ChannelId { get; set; }
/// <summary>
/// Gets or sets the position.
/// </summary>
[JsonProperty("position", NullValueHandling = NullValueHandling.Ignore)]
public int Position { get; set; }
}
/// <summary>
/// Represents a guild channel new parent payload.
/// </summary>
internal sealed class RestGuildChannelNewParentPayload
{
/// <summary>
/// Gets or sets the channel id.
/// </summary>
[JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)]
public ulong ChannelId { get; set; }
/// <summary>
/// Gets or sets the position.
/// </summary>
[JsonProperty("position", NullValueHandling = NullValueHandling.Ignore)]
public int Position { get; set; }
/// <summary>
/// Gets or sets the parent id.
/// </summary>
[JsonProperty("parent_id", NullValueHandling = NullValueHandling.Ignore)]
public Optional<ulong?> ParentId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether lock permissions.
/// </summary>
[JsonProperty("lock_permissions", NullValueHandling = NullValueHandling.Ignore)]
public bool? LockPermissions { get; set; }
}
/// <summary>
/// Represents a guild channel no parent payload.
/// </summary>
internal sealed class RestGuildChannelNoParentPayload
{
/// <summary>
/// Gets or sets the channel id.
/// </summary>
[JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)]
public ulong ChannelId { get; set; }
/// <summary>
/// Gets or sets the position.
/// </summary>
[JsonProperty("position", NullValueHandling = NullValueHandling.Ignore)]
public int Position { get; set; }
/// <summary>
/// Gets or sets the parent id.
/// </summary>
[JsonProperty("parent_id", NullValueHandling = NullValueHandling.Include)]
public Optional<ulong?> ParentId { get; set; }
}
/// <summary>
/// Represents a guild role reorder payload.
/// </summary>
internal sealed class RestGuildRoleReorderPayload
{
/// <summary>
/// Gets or sets the role id.
/// </summary>
[JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)]
public ulong RoleId { get; set; }
/// <summary>
/// Gets or sets the position.
/// </summary>
[JsonProperty("position", NullValueHandling = NullValueHandling.Ignore)]
public int Position { get; set; }
}
/// <summary>
/// Represents a guild member modify payload.
/// </summary>
internal sealed class RestGuildMemberModifyPayload
{
/// <summary>
/// Gets or sets the nickname.
/// </summary>
[JsonProperty("nick")]
public Optional<string> Nickname { get; set; }
/// <summary>
/// Gets or sets the role ids.
/// </summary>
[JsonProperty("roles")]
public Optional<IEnumerable<ulong>> RoleIds { get; set; }
/// <summary>
/// Gets or sets the mute.
/// </summary>
[JsonProperty("mute")]
public Optional<bool> Mute { get; set; }
/// <summary>
/// Gets or sets the deafen.
/// </summary>
[JsonProperty("deaf")]
public Optional<bool> Deafen { get; set; }
/// <summary>
/// Gets or sets the voice channel id.
/// </summary>
[JsonProperty("channel_id")]
public Optional<ulong?> VoiceChannelId { get; set; }
[JsonProperty("flags")]
public Optional<MemberFlags> Flags { get; set; }
}
/// <summary>
/// Represents a guild member timeout modify payload.
/// </summary>
internal sealed class RestGuildMemberTimeoutModifyPayload
{
/// <summary>
/// Gets or sets the date until the member can communicate again.
/// </summary>
[JsonProperty("communication_disabled_until")]
public DateTimeOffset? CommunicationDisabledUntil { get; internal set; }
}
/// <summary>
/// Represents a guild role payload.
/// </summary>
internal sealed class RestGuildRolePayload
{
/// <summary>
/// Gets or sets the name.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; set; }
/// <summary>
/// Gets or sets the permissions.
/// </summary>
[JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)]
public Permissions? Permissions { get; set; }
/// <summary>
/// Gets or sets the color.
/// </summary>
[JsonProperty("color", NullValueHandling = NullValueHandling.Ignore)]
public int? Color { get; set; }
/// <summary>
/// Gets or sets a value indicating whether hoist.
/// </summary>
[JsonProperty("hoist", NullValueHandling = NullValueHandling.Ignore)]
public bool? Hoist { get; set; }
/// <summary>
/// Gets or sets a value indicating whether mentionable.
/// </summary>
[JsonProperty("mentionable", NullValueHandling = NullValueHandling.Ignore)]
public bool? Mentionable { get; set; }
/// <summary>
/// Gets or sets the icon base64.
/// </summary>
[JsonProperty("icon")]
public Optional<string> IconBase64 { get; set; }
/// <summary>
/// Gets or sets the icon base64.
/// </summary>
[JsonProperty("unicode_emoji")]
public Optional<string> UnicodeEmoji { get; set; }
}
/// <summary>
/// Represents a guild prune result payload.
/// </summary>
internal sealed class RestGuildPruneResultPayload
{
/// <summary>
/// Gets or sets the pruned.
/// </summary>
[JsonProperty("pruned", NullValueHandling = NullValueHandling.Ignore)]
public int? Pruned { get; set; }
}
/// <summary>
/// Represents a guild integration attach payload.
/// </summary>
internal sealed class RestGuildIntegrationAttachPayload
{
/// <summary>
/// Gets or sets the type.
/// </summary>
[JsonProperty("type")]
public string Type { get; set; }
/// <summary>
/// Gets or sets the id.
/// </summary>
[JsonProperty("id")]
public ulong Id { get; set; }
}
/// <summary>
/// Represents a guild integration modify payload.
/// </summary>
internal sealed class RestGuildIntegrationModifyPayload
{
/// <summary>
/// Gets or sets the expire behavior.
/// </summary>
[JsonProperty("expire_behavior", NullValueHandling = NullValueHandling.Ignore)]
public int? ExpireBehavior { get; set; }
/// <summary>
/// Gets or sets the expire grace period.
/// </summary>
[JsonProperty("expire_grace_period", NullValueHandling = NullValueHandling.Ignore)]
public int? ExpireGracePeriod { get; set; }
/// <summary>
/// Gets or sets a value indicating whether enable emoticons.
/// </summary>
[JsonProperty("enable_emoticons", NullValueHandling = NullValueHandling.Ignore)]
public bool? EnableEmoticons { get; set; }
}
/// <summary>
/// Represents a guild emoji modify payload.
/// </summary>
internal class RestGuildEmojiModifyPayload
{
/// <summary>
/// Gets or sets the name.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Gets or sets the roles.
/// </summary>
[JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)]
public ulong[] Roles { get; set; }
}
/// <summary>
/// Represents a guild emoji create payload.
/// </summary>
internal class RestGuildEmojiCreatePayload : RestGuildEmojiModifyPayload
{
/// <summary>
/// Gets or sets the image b64.
/// </summary>
[JsonProperty("image")]
public string ImageB64 { get; set; }
}
/// <summary>
/// Represents a guild widget settings payload.
/// </summary>
internal class RestGuildWidgetSettingsPayload
{
/// <summary>
/// Gets or sets a value indicating whether enabled.
/// </summary>
[JsonProperty("enabled", NullValueHandling = NullValueHandling.Ignore)]
public bool? Enabled { get; set; }
/// <summary>
/// Gets or sets the channel id.
/// </summary>
[JsonProperty("channel_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? ChannelId { get; set; }
}
/// <summary>
/// Represents a guild template create or modify payload.
/// </summary>
internal class RestGuildTemplateCreateOrModifyPayload
{
/// <summary>
/// Gets or sets the name.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Include)]
public string Name { get; set; }
/// <summary>
/// Gets or sets the description.
/// </summary>
[JsonProperty("description", NullValueHandling = NullValueHandling.Include)]
public string Description { get; set; }
}
/// <summary>
/// Represents a guild membership screening form modify payload.
/// </summary>
internal class RestGuildMembershipScreeningFormModifyPayload
{
/// <summary>
/// Gets or sets the enabled.
/// </summary>
[JsonProperty("enabled", NullValueHandling = NullValueHandling.Ignore)]
public Optional<bool> Enabled { get; set; }
/// <summary>
/// Gets or sets the fields.
/// </summary>
[JsonProperty("form_fields", NullValueHandling = NullValueHandling.Ignore)]
public Optional<DiscordGuildMembershipScreeningField[]> Fields { get; set; }
/// <summary>
/// Gets or sets the description.
/// </summary>
[JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)]
public Optional<string> Description { get; set; }
}
/// <summary>
/// Represents a guild welcome screen modify payload.
/// </summary>
internal class RestGuildWelcomeScreenModifyPayload
{
/// <summary>
/// Gets or sets the enabled.
/// </summary>
[JsonProperty("enabled", NullValueHandling = NullValueHandling.Ignore)]
public Optional<bool> Enabled { get; set; }
/// <summary>
/// Gets or sets the welcome channels.
/// </summary>
[JsonProperty("welcome_channels", NullValueHandling = NullValueHandling.Ignore)]
public Optional<IEnumerable<DiscordGuildWelcomeScreenChannel>> WelcomeChannels { get; set; }
/// <summary>
/// Gets or sets the description.
/// </summary>
[JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)]
public Optional<string> Description { get; set; }
}
/// <summary>
/// Represents a guild update current user voice state payload.
/// </summary>
internal class RestGuildUpdateCurrentUserVoiceStatePayload
{
/// <summary>
/// Gets or sets the channel id.
/// </summary>
[JsonProperty("channel_id")]
public ulong ChannelId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether suppress.
/// </summary>
[JsonProperty("suppress", NullValueHandling = NullValueHandling.Ignore)]
public bool? Suppress { get; set; }
/// <summary>
/// Gets or sets the request to speak timestamp.
/// </summary>
[JsonProperty("request_to_speak_timestamp", NullValueHandling = NullValueHandling.Ignore)]
public DateTimeOffset? RequestToSpeakTimestamp { get; set; }
}
/// <summary>
/// Represents a guild update user voice state payload.
/// </summary>
internal class RestGuildUpdateUserVoiceStatePayload
{
/// <summary>
/// Gets or sets the channel id.
/// </summary>
[JsonProperty("channel_id")]
public ulong ChannelId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether suppress.
/// </summary>
[JsonProperty("suppress", NullValueHandling = NullValueHandling.Ignore)]
public bool? Suppress { get; set; }
}
diff --git a/DisCatSharp/Net/Abstractions/Rest/RestGuildScheduledEventPayloads.cs b/DisCatSharp/Net/Abstractions/Rest/RestGuildScheduledEventPayloads.cs
index d3904f4fa..7d823a863 100644
--- a/DisCatSharp/Net/Abstractions/Rest/RestGuildScheduledEventPayloads.cs
+++ b/DisCatSharp/Net/Abstractions/Rest/RestGuildScheduledEventPayloads.cs
@@ -1,150 +1,150 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Net.Abstractions;
/// <summary>
/// The rest guild scheduled event create payload.
/// </summary>
internal class RestGuildScheduledEventCreatePayload
{
/// <summary>
/// Gets or sets the channel id.
/// </summary>
[JsonProperty("channel_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? ChannelId { get; set; }
/// <summary>
/// Gets or sets the entity metadata.
/// </summary>
[JsonProperty("entity_metadata", NullValueHandling = NullValueHandling.Ignore)]
public DiscordScheduledEventEntityMetadata EntityMetadata { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Gets or sets the description.
/// </summary>
[JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)]
public string Description { get; set; }
/// <summary>
/// Gets or sets the privacy level of the scheduled event.
/// </summary>
[JsonProperty("privacy_level")]
public ScheduledEventPrivacyLevel PrivacyLevel { get; set; }
/// <summary>
/// Gets or sets the time to schedule the scheduled event.
/// </summary>
[JsonProperty("scheduled_start_time")]
public DateTimeOffset ScheduledStartTime { get; internal set; }
/// <summary>
/// Gets or sets the time when the scheduled event is scheduled to end.
/// </summary>
[JsonProperty("scheduled_end_time", NullValueHandling = NullValueHandling.Ignore)]
public DateTimeOffset? ScheduledEndTime { get; internal set; }
/// <summary>
/// Gets or sets the entity type of the scheduled event.
/// </summary>
[JsonProperty("entity_type")]
public ScheduledEventEntityType EntityType { get; set; }
/// <summary>
/// Gets or sets the image as base64.
/// </summary>
[JsonProperty("image", NullValueHandling = NullValueHandling.Include)]
public Optional<string> CoverBase64 { get; set; }
}
/// <summary>
/// The rest guild scheduled event modify payload.
/// </summary>
internal class RestGuildScheduledEventModifyPayload
{
/// <summary>
/// Gets or sets the channel id.
/// </summary>
[JsonProperty("channel_id")]
public Optional<ulong?> ChannelId { get; set; }
/// <summary>
/// Gets or sets the entity metadata.
/// </summary>
[JsonProperty("entity_metadata")]
public Optional<DiscordScheduledEventEntityMetadata> EntityMetadata { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
[JsonProperty("name")]
public Optional<string> Name { get; set; }
/// <summary>
/// Gets or sets the description.
/// </summary>
[JsonProperty("description")]
public Optional<string> Description { get; set; }
/// <summary>
/// Gets or sets the time to schedule the scheduled event.
/// </summary>
[JsonProperty("scheduled_start_time")]
public Optional<DateTimeOffset> ScheduledStartTime { get; internal set; }
/// <summary>
/// Gets or sets the time when the scheduled event is scheduled to end.
/// </summary>
[JsonProperty("scheduled_end_time")]
public Optional<DateTimeOffset> ScheduledEndTime { get; internal set; }
/// <summary>
/// Gets or sets the entity type of the scheduled event.
/// </summary>
[JsonProperty("entity_type")]
public Optional<ScheduledEventEntityType> EntityType { get; set; }
/// <summary>
/// Gets or sets the cover image as base64.
/// </summary>
[JsonProperty("image")]
public Optional<string> CoverBase64 { get; set; }
/// <summary>
/// Gets or sets the status of the scheduled event.
/// </summary>
[JsonProperty("status")]
public Optional<ScheduledEventStatus> Status { get; set; }
}
diff --git a/DisCatSharp/Net/Abstractions/Rest/RestStageInstancePayloads.cs b/DisCatSharp/Net/Abstractions/Rest/RestStageInstancePayloads.cs
index 004eb04b8..c22b5329e 100644
--- a/DisCatSharp/Net/Abstractions/Rest/RestStageInstancePayloads.cs
+++ b/DisCatSharp/Net/Abstractions/Rest/RestStageInstancePayloads.cs
@@ -1,70 +1,69 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Net.Abstractions;
/// <summary>
/// Represents a stage instance create payload.
/// </summary>
internal sealed class RestStageInstanceCreatePayload
{
/// <summary>
/// Gets or sets the channel id.
/// </summary>
[JsonProperty("channel_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong ChannelId { get; set; }
/// <summary>
/// Gets or sets the topic.
/// </summary>
[JsonProperty("topic", NullValueHandling = NullValueHandling.Ignore)]
public string Topic { get; set; }
/// <summary>
/// Gets or sets the associated scheduled event id.
/// </summary>
[JsonProperty("guild_scheduled_event_id", NullValueHandling = NullValueHandling.Ignore)]
public ulong? ScheduledEventId { get; set; }
/// <summary>
/// Whether everyone should be notified about the start.
/// </summary>
[JsonProperty("send_start_notification", NullValueHandling = NullValueHandling.Ignore)]
public bool SendStartNotification { get; set; }
}
/// <summary>
/// Represents a stage instance modify payload.
/// </summary>
internal sealed class RestStageInstanceModifyPayload
{
/// <summary>
/// Gets or sets the topic.
/// </summary>
[JsonProperty("topic", NullValueHandling = NullValueHandling.Ignore)]
public Optional<string> Topic { get; set; }
}
diff --git a/DisCatSharp/Net/Abstractions/Rest/RestStickerPayloads.cs b/DisCatSharp/Net/Abstractions/Rest/RestStickerPayloads.cs
index 712bcbf2f..32d942b3a 100644
--- a/DisCatSharp/Net/Abstractions/Rest/RestStickerPayloads.cs
+++ b/DisCatSharp/Net/Abstractions/Rest/RestStickerPayloads.cs
@@ -1,51 +1,51 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a sticker modify payload.
/// </summary>
internal class RestStickerModifyPayload
{
/// <summary>
/// Gets or sets the name.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public Optional<string> Name { get; set; }
/// <summary>
/// Gets or sets the description.
/// </summary>
[JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)]
public Optional<string> Description { get; set; }
/// <summary>
/// Gets or sets the tags.
/// </summary>
[JsonProperty("tags", NullValueHandling = NullValueHandling.Ignore)]
public Optional<string> Tags { get; set; }
}
diff --git a/DisCatSharp/Net/Abstractions/Rest/RestUserPayloads.cs b/DisCatSharp/Net/Abstractions/Rest/RestUserPayloads.cs
index 4703e0fb5..672b24603 100644
--- a/DisCatSharp/Net/Abstractions/Rest/RestUserPayloads.cs
+++ b/DisCatSharp/Net/Abstractions/Rest/RestUserPayloads.cs
@@ -1,155 +1,155 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Net.Abstractions;
/// <summary>
/// Represents a user dm create payload.
/// </summary>
internal sealed class RestUserDmCreatePayload
{
/// <summary>
/// Gets or sets the recipient.
/// </summary>
[JsonProperty("recipient_id")]
public ulong Recipient { get; set; }
}
/// <summary>
/// Represents a user group dm create payload.
/// </summary>
internal sealed class RestUserGroupDmCreatePayload
{
/// <summary>
/// Gets or sets the access tokens.
/// </summary>
[JsonProperty("access_tokens")]
public IEnumerable<string> AccessTokens { get; set; }
/// <summary>
/// Gets or sets the nicknames.
/// </summary>
[JsonProperty("nicks")]
public IDictionary<ulong, string> Nicknames { get; set; }
}
/// <summary>
/// Represents a user update current payload.
/// </summary>
internal sealed class RestUserUpdateCurrentPayload
{
/// <summary>
/// Gets or sets the username.
/// </summary>
[JsonProperty("username", NullValueHandling = NullValueHandling.Ignore)]
public string Username { get; set; }
/// <summary>
/// Gets or sets the avatar base64.
/// </summary>
[JsonProperty("avatar", NullValueHandling = NullValueHandling.Include)]
public string AvatarBase64 { get; set; }
/// <summary>
/// Gets or sets a value indicating whether avatar set.
/// </summary>
[JsonIgnore]
public bool AvatarSet { get; set; }
/// <summary>
/// Gets whether the avatar should be serialized.
/// </summary>
public bool ShouldSerializeAvatarBase64()
=> this.AvatarSet;
}
/// <summary>
/// Represents a user guild.
/// </summary>
internal sealed class RestUserGuild
{
/// <summary>
/// Gets the id.
/// </summary>
[JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)]
public ulong Id { get; set; }
/// <summary>
/// Gets the name.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
public string Name { get; set; }
/// <summary>
/// Gets the icon hash.
/// </summary>
[JsonProperty("icon", NullValueHandling = NullValueHandling.Ignore)]
public string IconHash { get; set; }
/// <summary>
/// Gets a value indicating whether is owner.
/// </summary>
[JsonProperty("owner", NullValueHandling = NullValueHandling.Ignore)]
public bool IsOwner { get; set; }
/// <summary>
/// Gets the permissions.
/// </summary>
[JsonProperty("permissions", NullValueHandling = NullValueHandling.Ignore)]
public Permissions Permissions { get; set; }
/// <summary>
/// Gets the guild features.
/// </summary>
[JsonProperty("features", NullValueHandling = NullValueHandling.Ignore)]
public List<string> Features { get; set; }
}
/// <summary>
/// Represents a user guild list payload.
/// </summary>
internal sealed class RestUserGuildListPayload
{
/// <summary>
/// Gets or sets the limit.
/// </summary>
[JsonProperty("limit", NullValueHandling = NullValueHandling.Ignore)]
public int Limit { get; set; }
/// <summary>
/// Gets or sets the before.
/// </summary>
[JsonProperty("before", NullValueHandling = NullValueHandling.Ignore)]
public ulong? Before { get; set; }
/// <summary>
/// Gets or sets the after.
/// </summary>
[JsonProperty("after", NullValueHandling = NullValueHandling.Ignore)]
public ulong? After { get; set; }
}
diff --git a/DisCatSharp/Net/Abstractions/Rest/RestWebhookPayloads.cs b/DisCatSharp/Net/Abstractions/Rest/RestWebhookPayloads.cs
index 8ccb8e18f..3f93a6528 100644
--- a/DisCatSharp/Net/Abstractions/Rest/RestWebhookPayloads.cs
+++ b/DisCatSharp/Net/Abstractions/Rest/RestWebhookPayloads.cs
@@ -1,161 +1,161 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a webhook payload.
/// </summary>
internal sealed class RestWebhookPayload
{
/// <summary>
/// Gets or sets the name.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// Gets or sets the avatar base64.
/// </summary>
[JsonProperty("avatar", NullValueHandling = NullValueHandling.Include)]
public string AvatarBase64 { get; set; }
/// <summary>
/// Gets or sets the channel id.
/// </summary>
[JsonProperty("channel_id")]
public ulong ChannelId { get; set; }
/// <summary>
/// Gets whether an avatar is set.
/// </summary>
[JsonProperty]
public bool AvatarSet { get; set; }
/// <summary>
/// Gets whether the avatar should be serialized.
/// </summary>
public bool ShouldSerializeAvatarBase64()
=> this.AvatarSet;
}
/// <summary>
/// Represents a webhook execute payload.
/// </summary>
internal sealed class RestWebhookExecutePayload
{
/// <summary>
/// Gets or sets the content.
/// </summary>
[JsonProperty("content", NullValueHandling = NullValueHandling.Ignore)]
public string Content { get; set; }
/// <summary>
/// Gets or sets the username.
/// </summary>
[JsonProperty("username", NullValueHandling = NullValueHandling.Ignore)]
public string Username { get; set; }
/// <summary>
/// Gets or sets the avatar url.
/// </summary>
[JsonProperty("avatar_url", NullValueHandling = NullValueHandling.Ignore)]
public string AvatarUrl { get; set; }
/// <summary>
/// Whether this message is tts.
/// </summary>
[JsonProperty("tts", NullValueHandling = NullValueHandling.Ignore)]
public bool? IsTts { get; set; }
/// <summary>
/// Gets or sets the embeds.
/// </summary>
[JsonProperty("embeds", NullValueHandling = NullValueHandling.Ignore)]
public IEnumerable<DiscordEmbed> Embeds { get; set; }
/// <summary>
/// Gets or sets the mentions.
/// </summary>
[JsonProperty("allowed_mentions", NullValueHandling = NullValueHandling.Ignore)]
public DiscordMentions Mentions { get; set; }
/// <summary>
/// Gets or sets the components.
/// </summary>
[JsonProperty("components", NullValueHandling = NullValueHandling.Ignore)]
public IEnumerable<DiscordActionRowComponent> Components { get; set; }
/// <summary>
/// Gets or sets the attachments.
/// </summary>
[JsonProperty("attachments", NullValueHandling = NullValueHandling.Ignore)]
public List<DiscordAttachment> Attachments { get; set; }
///<summary>
/// Gets or sets the thread name.
/// </summary>
[JsonProperty("thread_name", NullValueHandling = NullValueHandling.Ignore)]
public string ThreadName { get; set; }
}
/// <summary>
/// Represents a webhook message edit payload.
/// </summary>
internal sealed class RestWebhookMessageEditPayload
{
/// <summary>
/// Gets or sets the content.
/// </summary>
[JsonProperty("content", NullValueHandling = NullValueHandling.Ignore)]
public Optional<string> Content { get; set; }
/// <summary>
/// Gets or sets the embeds.
/// </summary>
[JsonProperty("embeds", NullValueHandling = NullValueHandling.Ignore)]
public IEnumerable<DiscordEmbed> Embeds { get; set; }
/// <summary>
/// Gets or sets the mentions.
/// </summary>
[JsonProperty("allowed_mentions", NullValueHandling = NullValueHandling.Ignore)]
public IEnumerable<IMention> Mentions { get; set; }
/// <summary>
/// Gets or sets the attachments.
/// </summary>
[JsonProperty("attachments", NullValueHandling = NullValueHandling.Ignore)]
public IEnumerable<DiscordAttachment> Attachments { get; set; }
/// <summary>
/// Gets or sets the components.
/// </summary>
[JsonProperty("components", NullValueHandling = NullValueHandling.Ignore)]
public IEnumerable<DiscordActionRowComponent> Components { get; set; }
}
diff --git a/DisCatSharp/Net/Abstractions/ShardInfo.cs b/DisCatSharp/Net/Abstractions/ShardInfo.cs
index c574798fb..e10f67025 100644
--- a/DisCatSharp/Net/Abstractions/ShardInfo.cs
+++ b/DisCatSharp/Net/Abstractions/ShardInfo.cs
@@ -1,97 +1,97 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
using Newtonsoft.Json.Linq;
namespace DisCatSharp.Net.Abstractions;
/// <summary>
/// Represents data for identify payload's shard info.
/// </summary>
[JsonConverter(typeof(ShardInfoConverter))]
internal sealed class ShardInfo
{
/// <summary>
/// Gets or sets this client's shard id.
/// </summary>
public int ShardId { get; set; }
/// <summary>
/// Gets or sets the total shard count for this token.
/// </summary>
public int ShardCount { get; set; }
}
/// <summary>
/// Represents a shard info converter.
/// </summary>
internal sealed class ShardInfoConverter : JsonConverter
{
/// <summary>
/// Writes the json.
/// </summary>
/// <param name="writer">The writer.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The serializer.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var sinfo = value as ShardInfo;
var obj = new object[] { sinfo.ShardId, sinfo.ShardCount };
serializer.Serialize(writer, obj);
}
/// <summary>
/// Reads the json.
/// </summary>
/// <param name="reader">The reader.</param>
/// <param name="objectType">The object type.</param>
/// <param name="existingValue">The existing value.</param>
/// <param name="serializer">The serializer.</param>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var arr = this.ReadArrayObject(reader, serializer);
return new ShardInfo
{
ShardId = (int)arr[0],
ShardCount = (int)arr[1],
};
}
/// <summary>
/// Reads the array object.
/// </summary>
/// <param name="reader">The reader.</param>
/// <param name="serializer">The serializer.</param>
private JArray ReadArrayObject(JsonReader reader, JsonSerializer serializer) =>
serializer.Deserialize<JToken>(reader) is not JArray arr || arr.Count != 2
? throw new JsonSerializationException("Expected array of length 2")
: arr;
/// <summary>
/// Whether this can be converted.
/// </summary>
/// <param name="objectType">The object type.</param>
public override bool CanConvert(Type objectType) => objectType == typeof(ShardInfo);
}
diff --git a/DisCatSharp/Net/Abstractions/StatusUpdate.cs b/DisCatSharp/Net/Abstractions/StatusUpdate.cs
index 76ca6b85e..1201b98cd 100644
--- a/DisCatSharp/Net/Abstractions/StatusUpdate.cs
+++ b/DisCatSharp/Net/Abstractions/StatusUpdate.cs
@@ -1,74 +1,74 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents data for websocket status update payload.
/// </summary>
internal sealed class StatusUpdate
{
/// <summary>
/// Gets or sets the unix millisecond timestamp of when the user went idle.
/// </summary>
[JsonProperty("since", NullValueHandling = NullValueHandling.Include)]
public long? IdleSince { get; set; }
/// <summary>
/// Gets or sets whether the user is AFK.
/// </summary>
[JsonProperty("afk")]
public bool IsAfk { get; set; }
/// <summary>
/// Gets or sets the status of the user.
/// </summary>
[JsonIgnore]
public UserStatus Status { get; set; } = UserStatus.Online;
/// <summary>
/// Gets the status string of the user.
/// </summary>
[JsonProperty("status")]
internal string StatusString =>
this.Status switch
{
UserStatus.Online => "online",
UserStatus.Idle => "idle",
UserStatus.DoNotDisturb => "dnd",
UserStatus.Invisible or UserStatus.Offline => "invisible",
UserStatus.Streaming => "streaming",
_ => "online",
};
/// <summary>
/// Gets or sets the game the user is playing.
/// </summary>
[JsonProperty("game", NullValueHandling = NullValueHandling.Ignore)]
public TransportActivity Activity { get; set; }
internal DiscordActivity ActivityInternal;
}
diff --git a/DisCatSharp/Net/Abstractions/Transport/TransportActivity.cs b/DisCatSharp/Net/Abstractions/Transport/TransportActivity.cs
index c59d05278..64b5b6812 100644
--- a/DisCatSharp/Net/Abstractions/Transport/TransportActivity.cs
+++ b/DisCatSharp/Net/Abstractions/Transport/TransportActivity.cs
@@ -1,373 +1,373 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a game a user is playing.
/// </summary>
internal sealed class TransportActivity
{
/// <summary>
/// Gets or sets the id of user's activity.
/// </summary>
[JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)]
public string Id { get; internal set; }
/// <summary>
/// Gets or sets the name of the game the user is playing.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Include)]
public string Name { get; internal set; }
/// <summary>
/// Gets or sets the stream URI, if applicable.
/// </summary>
[JsonProperty("url", NullValueHandling = NullValueHandling.Ignore)]
public string StreamUrl { get; internal set; }
/// <summary>
/// Gets or sets the livestream type.
/// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
public ActivityType ActivityType { get; internal set; }
/// <summary>
/// Gets or sets the details.
///
/// <para>This is a component of the rich presence, and, as such, can only be used by regular users.</para>
/// </summary>
[JsonProperty("details", NullValueHandling = NullValueHandling.Ignore)]
public string Details { get; internal set; }
/// <summary>
/// Gets or sets game state.
///
/// <para>This is a component of the rich presence, and, as such, can only be used by regular users.</para>
/// </summary>
[JsonProperty("state", NullValueHandling = NullValueHandling.Ignore)]
public string State { get; internal set; }
/// <summary>
/// Gets the emoji details for a custom status, if any.
/// </summary>
[JsonProperty("emoji", NullValueHandling = NullValueHandling.Ignore)]
public DiscordEmoji Emoji { get; internal set; }
/// <summary>
/// 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.
/// </summary>
[JsonIgnore]
public ulong? ApplicationId
{
get => this.ApplicationIdStr != null ? ulong.Parse(this.ApplicationIdStr, CultureInfo.InvariantCulture) : null;
internal set => this.ApplicationIdStr = value?.ToString(CultureInfo.InvariantCulture);
}
/// <summary>
/// Gets or sets the application id string.
/// </summary>
[JsonProperty("application_id", NullValueHandling = NullValueHandling.Ignore)]
internal string ApplicationIdStr { get; set; }
/// <summary>
/// Gets or sets instance status.
///
/// This is a component of the rich presence, and, as such, can only be used by regular users.
/// </summary>
[JsonProperty("instance", NullValueHandling = NullValueHandling.Ignore)]
public bool? Instance { get; internal set; }
/// <summary>
/// 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.
/// </summary>
[JsonProperty("party", NullValueHandling = NullValueHandling.Ignore)]
public GameParty Party { get; internal set; }
/// <summary>
/// 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.
/// </summary>
[JsonProperty("assets", NullValueHandling = NullValueHandling.Ignore)]
public PresenceAssets Assets { get; internal set; }
/// <summary>
/// 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.
/// </summary>
[JsonProperty("buttons", NullValueHandling = NullValueHandling.Ignore)]
public IReadOnlyList<string> Buttons { get; internal set; }
/// <summary>
/// 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.
/// </summary>
[JsonProperty("platform", NullValueHandling = NullValueHandling.Ignore)]
public string Platform { get; internal set; }
/// <summary>
/// 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.
/// </summary>
[JsonProperty("sync_id", NullValueHandling = NullValueHandling.Ignore)]
public string SyncId { get; internal set; }
/// <summary>
/// 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.
/// </summary>
[JsonProperty("session_id", NullValueHandling = NullValueHandling.Ignore)]
public string SessionId { get; internal set; }
/// <summary>
/// Gets or sets information about current game's timestamps.
///
/// This is a component of the rich presence, and, as such, can only be used by regular users.
/// </summary>
[JsonProperty("timestamps", NullValueHandling = NullValueHandling.Ignore)]
public GameTimestamps Timestamps { get; internal set; }
/// <summary>
/// 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.
/// </summary>
[JsonProperty("secrets", NullValueHandling = NullValueHandling.Ignore)]
public GameSecrets Secrets { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="TransportActivity"/> class.
/// </summary>
internal TransportActivity() { }
/// <summary>
/// Initializes a new instance of the <see cref="TransportActivity"/> class.
/// </summary>
/// <param name="game">The game.</param>
internal TransportActivity(DiscordActivity game)
{
if (game == null)
return;
this.Name = game.Name;
this.ActivityType = game.ActivityType;
this.StreamUrl = game.StreamUrl;
}
/// <summary>
/// Whether this activity is a rich presence.
/// </summary>
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;
/// <summary>
/// Whether this activity is a custom status.
/// </summary>
public bool IsCustomStatus()
=> this.Name == "Custom Status";
/// <summary>
/// Represents information about assets attached to a rich presence.
/// </summary>
public class PresenceAssets
{
/// <summary>
/// Gets the large image asset ID.
/// </summary>
[JsonProperty("large_image")]
public string LargeImage { get; set; }
/// <summary>
/// Gets the large image text.
/// </summary>
[JsonProperty("large_text", NullValueHandling = NullValueHandling.Ignore)]
public string LargeImageText { get; internal set; }
/// <summary>
/// Gets the small image asset ID.
/// </summary>
[JsonProperty("small_image")]
internal string SmallImage { get; set; }
/// <summary>
/// Gets the small image text.
/// </summary>
[JsonProperty("small_text", NullValueHandling = NullValueHandling.Ignore)]
public string SmallImageText { get; internal set; }
}
/// <summary>
/// Represents information about rich presence game party.
/// </summary>
public class GameParty
{
/// <summary>
/// Gets the game party ID.
/// </summary>
[JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)]
public string Id { get; internal set; }
/// <summary>
/// Gets the size of the party.
/// </summary>
[JsonProperty("size", NullValueHandling = NullValueHandling.Ignore)]
public GamePartySize Size { get; internal set; }
/// <summary>
/// Represents information about party size.
/// </summary>
[JsonConverter(typeof(GamePartySizeConverter))]
public class GamePartySize
{
/// <summary>
/// Gets the current number of players in the party.
/// </summary>
public long Current { get; internal set; }
/// <summary>
/// Gets the maximum party size.
/// </summary>
public long Maximum { get; internal set; }
}
}
/// <summary>
/// Represents information about the game state's timestamps.
/// </summary>
public class GameTimestamps
{
/// <summary>
/// Gets the time the game has started.
/// </summary>
[JsonIgnore]
public DateTimeOffset? Start
=> this.StartInternal != null ? Utilities.GetDateTimeOffsetFromMilliseconds(this.StartInternal.Value, false) : null;
[JsonProperty("start", NullValueHandling = NullValueHandling.Ignore)]
internal long? StartInternal;
/// <summary>
/// Gets the time the game is going to end.
/// </summary>
[JsonIgnore]
public DateTimeOffset? End
=> this.EndInternal != null ? Utilities.GetDateTimeOffsetFromMilliseconds(this.EndInternal.Value, false) : null;
[JsonProperty("end", NullValueHandling = NullValueHandling.Ignore)]
internal long? EndInternal;
}
/// <summary>
/// Represents information about secret values for the Join, Spectate, and Match actions.
/// </summary>
public class GameSecrets
{
/// <summary>
/// Gets the secret value for join action.
/// </summary>
[JsonProperty("join", NullValueHandling = NullValueHandling.Ignore)]
public string Join { get; internal set; }
/// <summary>
/// Gets the secret value for match action.
/// </summary>
[JsonProperty("match", NullValueHandling = NullValueHandling.Ignore)]
public string Match { get; internal set; }
/// <summary>
/// Gets the secret value for spectate action.
/// </summary>
[JsonProperty("spectate", NullValueHandling = NullValueHandling.Ignore)]
public string Spectate { get; internal set; }
}
}
/// <summary>
/// Represents a game party size converter.
/// </summary>
internal sealed class GamePartySizeConverter : JsonConverter
{
/// <summary>
/// Writes the json.
/// </summary>
/// <param name="writer">The writer.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The serializer.</param>
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);
}
/// <summary>
/// Reads the json.
/// </summary>
/// <param name="reader">The reader.</param>
/// <param name="objectType">The object type.</param>
/// <param name="existingValue">The existing value.</param>
/// <param name="serializer">The serializer.</param>
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],
};
}
/// <summary>
/// Reads the array object.
/// </summary>
/// <param name="reader">The reader.</param>
/// <param name="serializer">The serializer.</param>
private JArray ReadArrayObject(JsonReader reader, JsonSerializer serializer) =>
serializer.Deserialize<JToken>(reader) is not JArray arr || arr.Count != 2
? throw new JsonSerializationException("Expected array of length 2")
: arr;
/// <summary>
/// Whether it can convert.
/// </summary>
/// <param name="objectType">The object type.</param>
public override bool CanConvert(Type objectType) => objectType == typeof(TransportActivity.GameParty.GamePartySize);
}
diff --git a/DisCatSharp/Net/Abstractions/Transport/TransportApplication.cs b/DisCatSharp/Net/Abstractions/Transport/TransportApplication.cs
index 8b3e493c1..2dd01e254 100644
--- a/DisCatSharp/Net/Abstractions/Transport/TransportApplication.cs
+++ b/DisCatSharp/Net/Abstractions/Transport/TransportApplication.cs
@@ -1,186 +1,186 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// The transport application.
/// </summary>
internal sealed class TransportApplication
{
/// <summary>
/// Gets or sets the id.
/// </summary>
[JsonProperty("id", NullValueHandling = NullValueHandling.Include)]
public ulong Id { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Include)]
public string Name { get; set; }
/// <summary>
/// Gets or sets the icon hash.
/// </summary>
[JsonProperty("icon", NullValueHandling = NullValueHandling.Include)]
public string IconHash { get; set; }
/// <summary>
/// Gets or sets the description.
/// </summary>
[JsonProperty("description", NullValueHandling = NullValueHandling.Include)]
public string Description { get; set; }
/// <summary>
/// Gets or sets the summary.
/// </summary>
[JsonProperty("summary", NullValueHandling = NullValueHandling.Include)]
public string Summary { get; set; }
/// <summary>
/// Whether the bot is public.
/// </summary>
[JsonProperty("bot_public", NullValueHandling = NullValueHandling.Include)]
public bool IsPublicBot { get; set; }
/// <summary>
/// Gets or sets the flags.
/// </summary>
[JsonProperty("flags", NullValueHandling = NullValueHandling.Include)]
public ApplicationFlags Flags { get; set; }
/// <summary>
/// Gets or sets the terms of service url.
/// </summary>
[JsonProperty("terms_of_service_url", NullValueHandling = NullValueHandling.Include)]
public string TermsOfServiceUrl { get; set; }
/// <summary>
/// Gets or sets the privacy policy url.
/// </summary>
[JsonProperty("privacy_policy_url", NullValueHandling = NullValueHandling.Include)]
public string PrivacyPolicyUrl { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the bot requires code grant.
/// </summary>
[JsonProperty("bot_require_code_grant", NullValueHandling = NullValueHandling.Include)]
public bool BotRequiresCodeGrant { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the bot is a hook.
/// </summary>
[JsonProperty("hook", NullValueHandling = NullValueHandling.Ignore)]
public bool IsHook { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the bot requires code grant.
/// </summary>
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)]
public string Type { get; set; }
/// <summary>
/// Gets or sets the rpc origins.
/// </summary>
[JsonProperty("rpc_origins", NullValueHandling = NullValueHandling.Ignore)]
public IList<string> RpcOrigins { get; set; }
/// <summary>
/// Gets or sets the owner.
/// </summary>
[JsonProperty("owner", NullValueHandling = NullValueHandling.Include)]
public TransportUser Owner { get; set; }
/// <summary>
/// Gets or sets the team.
/// </summary>
[JsonProperty("team", NullValueHandling = NullValueHandling.Include)]
public TransportTeam Team { get; set; }
/// <summary>
/// Gets or sets the verify key.
/// </summary>
[JsonProperty("verify_key", NullValueHandling = NullValueHandling.Include)]
public Optional<string> VerifyKey { get; set; }
/// <summary>
/// Gets or sets the guild id.
/// </summary>
[JsonProperty("guild_id")]
public Optional<ulong> GuildId { get; set; }
/// <summary>
/// Gets or sets the primary sku id.
/// </summary>
[JsonProperty("primary_sku_id")]
public Optional<ulong> PrimarySkuId { get; set; }
/// <summary>
/// Gets or sets the slug.
/// </summary>
[JsonProperty("slug")]
public Optional<string> Slug { get; set; }
/// <summary>
/// Gets or sets the cover image hash.
/// </summary>
[JsonProperty("cover_image")]
public Optional<string> CoverImageHash { get; set; }
/// <summary>
/// Gets or sets the custom install url.
/// </summary>
[JsonProperty("custom_install_url")]
public string CustomInstallUrl { get; set; }
/// <summary>
/// Gets or sets the install params.
/// </summary>
[JsonProperty("install_params", NullValueHandling = NullValueHandling.Include)]
public DiscordApplicationInstallParams InstallParams { get; set; }
/// <summary>
/// Gets or sets the role connection verification entry point.
/// </summary>
[JsonProperty("role_connections_verification_url")]
public string RoleConnectionsVerificationUrl { get; set; }
/// <summary>
/// Gets or sets the tags.
/// </summary>
[JsonProperty("tags", NullValueHandling = NullValueHandling.Include)]
public List<string> Tags { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="TransportApplication"/> class.
/// </summary>
internal TransportApplication()
{ }
}
diff --git a/DisCatSharp/Net/Abstractions/Transport/TransportMember.cs b/DisCatSharp/Net/Abstractions/Transport/TransportMember.cs
index 0c2aa4121..5476069f5 100644
--- a/DisCatSharp/Net/Abstractions/Transport/TransportMember.cs
+++ b/DisCatSharp/Net/Abstractions/Transport/TransportMember.cs
@@ -1,127 +1,127 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using DisCatSharp.Enums;
using Newtonsoft.Json;
namespace DisCatSharp.Net.Abstractions;
/// <summary>
/// Represents a transport member.
/// </summary>
internal class TransportMember
{
/// <summary>
/// Gets the avatar hash.
/// </summary>
[JsonIgnore]
public string AvatarHash { get; internal set; }
/// <summary>
/// Gets the guild avatar hash.
/// </summary>
[JsonProperty("avatar", NullValueHandling = NullValueHandling.Ignore)]
public string GuildAvatarHash { get; internal set; }
/// <summary>
/// Gets the guild banner hash.
/// </summary>
[JsonProperty("banner", NullValueHandling = NullValueHandling.Ignore)]
public string GuildBannerHash { get; internal set; }
/// <summary>
/// Gets the guild bio.
/// This is not available to bots tho.
/// </summary>
[JsonProperty("bio", NullValueHandling = NullValueHandling.Ignore)]
public string GuildBio { get; internal set; }
/// <summary>
/// Gets the members's pronouns.
/// </summary>
[JsonProperty("pronouns", NullValueHandling = NullValueHandling.Ignore)]
public string GuildPronouns { get; internal set; }
/// <summary>
/// Gets the user.
/// </summary>
[JsonProperty("user", NullValueHandling = NullValueHandling.Ignore)]
public TransportUser User { get; internal set; }
/// <summary>
/// Gets the nickname.
/// </summary>
[JsonProperty("nick", NullValueHandling = NullValueHandling.Ignore)]
public string Nickname { get; internal set; }
/// <summary>
/// Gets the roles.
/// </summary>
[JsonProperty("roles", NullValueHandling = NullValueHandling.Ignore)]
public List<ulong> Roles { get; internal set; }
/// <summary>
/// Gets the joined at.
/// </summary>
[JsonProperty("joined_at", NullValueHandling = NullValueHandling.Ignore)]
public DateTime JoinedAt { get; internal set; }
/// <summary>
/// Whether this member is deafened.
/// </summary>
[JsonProperty("deaf", NullValueHandling = NullValueHandling.Ignore)]
public bool IsDeafened { get; internal set; }
/// <summary>
/// Whether this member is muted.
/// </summary>
[JsonProperty("mute", NullValueHandling = NullValueHandling.Ignore)]
public bool IsMuted { get; internal set; }
/// <summary>
/// Gets the premium since.
/// </summary>
[JsonProperty("premium_since", NullValueHandling = NullValueHandling.Ignore)]
public DateTime? PremiumSince { get; internal set; }
/// <summary>
/// Whether this member is marked as pending.
/// </summary>
[JsonProperty("pending", NullValueHandling = NullValueHandling.Ignore)]
public bool? IsPending { get; internal set; }
/// <summary>
/// Gets the timeout time.
/// </summary>
[JsonProperty("communication_disabled_until", NullValueHandling = NullValueHandling.Include)]
public DateTime? CommunicationDisabledUntil { get; internal set; }
/// <summary>
/// Gets the members flags.
/// </summary>
[JsonProperty("flags", NullValueHandling = NullValueHandling.Ignore)]
public MemberFlags MemberFlags { get; internal set; }
}
diff --git a/DisCatSharp/Net/Abstractions/Transport/TransportTeam.cs b/DisCatSharp/Net/Abstractions/Transport/TransportTeam.cs
index 86e6d5305..668351a08 100644
--- a/DisCatSharp/Net/Abstractions/Transport/TransportTeam.cs
+++ b/DisCatSharp/Net/Abstractions/Transport/TransportTeam.cs
@@ -1,103 +1,103 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net.Abstractions;
/// <summary>
/// The transport team.
/// </summary>
internal sealed class TransportTeam
{
/// <summary>
/// Gets or sets the id.
/// </summary>
[JsonProperty("id")]
public ulong Id { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
[JsonProperty("name", NullValueHandling = NullValueHandling.Include)]
public string Name { get; set; }
/// <summary>
/// Gets or sets the icon hash.
/// </summary>
[JsonProperty("icon", NullValueHandling = NullValueHandling.Include)]
public string IconHash { get; set; }
/// <summary>
/// Gets or sets the owner id.
/// </summary>
[JsonProperty("owner_user_id")]
public ulong OwnerId { get; set; }
/// <summary>
/// Gets or sets the members.
/// </summary>
[JsonProperty("members", NullValueHandling = NullValueHandling.Include)]
public List<TransportTeamMember> Members { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="TransportTeam"/> class.
/// </summary>
internal TransportTeam() { }
}
/// <summary>
/// The transport team member.
/// </summary>
internal sealed class TransportTeamMember
{
/// <summary>
/// Gets or sets the membership state.
/// </summary>
[JsonProperty("membership_state")]
public int MembershipState { get; set; }
/// <summary>
/// Gets or sets the permissions.
/// </summary>
[JsonProperty("permissions", NullValueHandling = NullValueHandling.Include)]
public List<string> Permissions { get; set; }
/// <summary>
/// Gets or sets the team id.
/// </summary>
[JsonProperty("team_id")]
public ulong TeamId { get; set; }
/// <summary>
/// Gets or sets the user.
/// </summary>
[JsonProperty("user", NullValueHandling = NullValueHandling.Include)]
public TransportUser User { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="TransportTeamMember"/> class.
/// </summary>
internal TransportTeamMember() { }
}
diff --git a/DisCatSharp/Net/Abstractions/Transport/TransportUser.cs b/DisCatSharp/Net/Abstractions/Transport/TransportUser.cs
index 9ef75c175..685f70542 100644
--- a/DisCatSharp/Net/Abstractions/Transport/TransportUser.cs
+++ b/DisCatSharp/Net/Abstractions/Transport/TransportUser.cs
@@ -1,180 +1,180 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net.Abstractions;
/// <summary>
/// Represents a transport user.
/// </summary>
internal class TransportUser
{
/// <summary>
/// Gets the id.
/// </summary>
[JsonProperty("id")]
public ulong Id { get; internal set; }
/// <summary>
/// Gets the username.
/// </summary>
[JsonProperty("username", NullValueHandling = NullValueHandling.Ignore)]
public string Username { get; internal set; }
/// <summary>
/// Gets or sets the discriminator.
/// </summary>
[JsonProperty("discriminator", NullValueHandling = NullValueHandling.Ignore)]
internal string Discriminator { get; set; }
/// <summary>
/// Gets the username with discriminator.
/// </summary>
[JsonIgnore]
internal string UsernameWithDiscriminator
=> $"{this.Username}#{this.Discriminator}";
/// <summary>
/// Gets the avatar hash.
/// </summary>
[JsonProperty("avatar", NullValueHandling = NullValueHandling.Ignore)]
public string AvatarHash { get; internal set; }
/// <summary>
/// Gets the avatar decoration hash.
/// </summary>
[JsonProperty("avatar_decoration", NullValueHandling = NullValueHandling.Ignore)]
public string AvatarDecorationHash { get; internal set; }
/// <summary>
/// Gets the banner hash.
/// </summary>
[JsonProperty("banner", NullValueHandling = NullValueHandling.Ignore)]
public string BannerHash { get; internal set; }
/// <summary>
/// Gets the banner color.
/// </summary>
[JsonProperty("accent_color", NullValueHandling = NullValueHandling.Ignore)]
public int? BannerColor { get; internal set; }
/// <summary>
/// Gets the users theme colors.
/// </summary>
[JsonProperty("theme_colors", NullValueHandling = NullValueHandling.Ignore)]
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public int[]? ThemeColors { get; internal set; }
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
/// <summary>
/// Gets a value indicating whether is bot.
/// </summary>
[JsonProperty("bot", NullValueHandling = NullValueHandling.Ignore)]
public bool IsBot { get; internal set; }
/// <summary>
/// Gets a value indicating whether mfa enabled.
/// </summary>
[JsonProperty("mfa_enabled", NullValueHandling = NullValueHandling.Ignore)]
public bool? MfaEnabled { get; internal set; }
/// <summary>
/// Gets a value indicating whether verified.
/// </summary>
[JsonProperty("verified", NullValueHandling = NullValueHandling.Ignore)]
public bool? Verified { get; internal set; }
/// <summary>
/// Gets the email.
/// </summary>
[JsonProperty("email", NullValueHandling = NullValueHandling.Ignore)]
public string Email { get; internal set; }
/// <summary>
/// Gets the premium type.
/// </summary>
[JsonProperty("premium_type", NullValueHandling = NullValueHandling.Ignore)]
public PremiumType? PremiumType { get; internal set; }
/// <summary>
/// Gets the locale.
/// </summary>
[JsonProperty("locale", NullValueHandling = NullValueHandling.Ignore)]
public string Locale { get; internal set; }
/// <summary>
/// Gets the OAuth flags.
/// </summary>
[JsonProperty("flags", NullValueHandling = NullValueHandling.Ignore)]
public UserFlags? OAuthFlags { get; internal set; }
/// <summary>
/// Gets the flags.
/// </summary>
[JsonProperty("public_flags", NullValueHandling = NullValueHandling.Ignore)]
public UserFlags? Flags { get; internal set; }
/// <summary>
/// Gets the users bio.
/// This is not available to bots tho.
/// </summary>
[JsonProperty("bio", NullValueHandling = NullValueHandling.Ignore)]
public string Bio { get; internal set; }
/// <summary>
/// Gets the users pronouns.
/// </summary>
[JsonProperty("pronouns", NullValueHandling = NullValueHandling.Ignore)]
public string Pronouns { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="TransportUser"/> class.
/// </summary>
internal TransportUser() { }
/// <summary>
/// Initializes a new instance of the <see cref="TransportUser"/> class from an existing <see cref="TransportUser"/>.
/// </summary>
/// <param name="other">The other transport user.</param>
internal TransportUser(TransportUser other)
{
this.Id = other.Id;
this.Username = other.Username;
this.Discriminator = other.Discriminator;
this.AvatarHash = other.AvatarHash;
this.BannerHash = other.BannerHash;
this.BannerColor = other.BannerColor;
this.IsBot = other.IsBot;
this.MfaEnabled = other.MfaEnabled;
this.Verified = other.Verified;
this.Email = other.Email;
this.PremiumType = other.PremiumType;
this.Locale = other.Locale;
this.Flags = other.Flags;
this.OAuthFlags = other.OAuthFlags;
this.Bio = other.Bio;
this.Pronouns = other.Pronouns;
}
}
diff --git a/DisCatSharp/Net/Abstractions/VoiceStateUpdate.cs b/DisCatSharp/Net/Abstractions/VoiceStateUpdate.cs
index 5783f2eef..ee9fab636 100644
--- a/DisCatSharp/Net/Abstractions/VoiceStateUpdate.cs
+++ b/DisCatSharp/Net/Abstractions/VoiceStateUpdate.cs
@@ -1,55 +1,55 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net.Abstractions;
/// <summary>
/// Represents data for websocket voice state update payload.
/// </summary>
internal sealed class VoiceStateUpdate
{
/// <summary>
/// Gets or sets the guild for which the user is updating their voice state.
/// </summary>
[JsonProperty("guild_id")]
public ulong GuildId { get; set; }
/// <summary>
/// Gets or sets the channel user wants to connect to. Null if disconnecting.
/// </summary>
[JsonProperty("channel_id")]
public ulong? ChannelId { get; set; }
/// <summary>
/// Gets or sets whether the client is muted.
/// </summary>
[JsonProperty("self_mute")]
public bool Mute { get; set; }
/// <summary>
/// Gets or sets whether the client is deafened.
/// </summary>
[JsonProperty("self_deaf")]
public bool Deafen { get; set; }
}
diff --git a/DisCatSharp/Net/ConnectionEndpoint.cs b/DisCatSharp/Net/ConnectionEndpoint.cs
index fe826fd27..e9f959838 100644
--- a/DisCatSharp/Net/ConnectionEndpoint.cs
+++ b/DisCatSharp/Net/ConnectionEndpoint.cs
@@ -1,87 +1,87 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net;
/// <summary>
/// Represents a network connection endpoint.
/// </summary>
public struct ConnectionEndpoint
{
/// <summary>
/// Gets or sets the hostname associated with this endpoint.
/// </summary>
public string Hostname { get; set; }
/// <summary>
/// Gets or sets the port associated with this endpoint.
/// </summary>
public int Port { get; set; }
/// <summary>
/// Gets or sets the secured status of this connection.
/// </summary>
public bool Secured { get; set; }
/// <summary>
/// Creates a new endpoint structure.
/// </summary>
/// <param name="hostname">Hostname to connect to.</param>
/// <param name="port">Port to use for connection.</param>
/// <param name="secured">Whether the connection should be secured (https/wss).</param>
public ConnectionEndpoint(string hostname, int port, bool secured = false)
{
this.Hostname = hostname;
this.Port = port;
this.Secured = secured;
}
/// <summary>
/// Gets the hash code of this endpoint.
/// </summary>
/// <returns>Hash code of this endpoint.</returns>
public override int GetHashCode() => 13 + (7 * this.Hostname.GetHashCode()) + (7 * this.Port);
/// <summary>
/// Gets the string representation of this connection endpoint.
/// </summary>
/// <returns>String representation of this endpoint.</returns>
public override string ToString() => $"{this.Hostname}:{this.Port}";
/// <summary>
/// Returns a http string.
/// </summary>
internal string ToHttpString()
{
var secure = this.Secured ? "s" : "";
return $"http{secure}://{this}";
}
/// <summary>
/// Returns a web socket string.
/// </summary>
internal string ToWebSocketString()
{
var secure = this.Secured ? "s" : "";
return $"ws{secure}://{this}/";
}
}
diff --git a/DisCatSharp/Net/Models/ApplicationCommandEditModel.cs b/DisCatSharp/Net/Models/ApplicationCommandEditModel.cs
index e752c70f5..6e876785f 100644
--- a/DisCatSharp/Net/Models/ApplicationCommandEditModel.cs
+++ b/DisCatSharp/Net/Models/ApplicationCommandEditModel.cs
@@ -1,97 +1,97 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using DisCatSharp.Entities;
using DisCatSharp.Enums;
namespace DisCatSharp.Net.Models;
/// <summary>
/// Represents a application command edit model.
/// </summary>
public class ApplicationCommandEditModel
{
/// <summary>
/// Sets the command's new name.
/// </summary>
public Optional<string> Name
{
internal get => this._name;
set
{
if (value.Value.Length > 32)
throw new ArgumentException("Application command name cannot exceed 32 characters.", nameof(value));
this._name = value;
}
}
private Optional<string> _name;
/// <summary>
/// Sets the command's new description
/// </summary>
public Optional<string> Description
{
internal get => this._description;
set
{
if (value.Value.Length > 100)
throw new ArgumentException("Application command description cannot exceed 100 characters.", nameof(value));
this._description = value;
}
}
private Optional<string> _description;
/// <summary>
/// Sets the command's name localizations.
/// </summary>
public Optional<DiscordApplicationCommandLocalization> NameLocalizations { internal get; set; }
/// <summary>
/// Sets the command's description localizations.
/// </summary>
public Optional<DiscordApplicationCommandLocalization> DescriptionLocalizations { internal get; set; }
/// <summary>
/// Sets the command's new options.
/// </summary>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public Optional<List<DiscordApplicationCommandOption>?> Options { internal get; set; }
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
/// <summary>
/// Sets the command's needed permissions.
/// </summary>
public Optional<Permissions?> DefaultMemberPermissions { internal get; set; }
/// <summary>
/// Sets whether the command can be used in direct messages.
/// </summary>
public Optional<bool> DmPermission { internal get; set; }
/// <summary>
/// Sets whether the command is marked as NSFW.
/// </summary>
public Optional<bool> IsNsfw { internal get; set; }
}
diff --git a/DisCatSharp/Net/Models/AutomodRuleEditModel.cs b/DisCatSharp/Net/Models/AutomodRuleEditModel.cs
index c9f662cca..648dc712a 100644
--- a/DisCatSharp/Net/Models/AutomodRuleEditModel.cs
+++ b/DisCatSharp/Net/Models/AutomodRuleEditModel.cs
@@ -1,49 +1,49 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
namespace DisCatSharp.Net.Models
{
/// <summary>
/// Represents an automod rule edit model.
/// </summary>
public class AutomodRuleEditModel : BaseEditModel
{
public Optional<string> Name { internal get; set; }
public Optional<AutomodEventType> EventType { internal get; set; }
public Optional<AutomodTriggerMetadata> TriggerMetadata { internal get; set; }
public Optional<List<AutomodAction>> Actions { internal get; set; }
public Optional<bool> Enabled { internal get; set; }
public Optional<List<ulong>> ExemptRoles { internal get; set; }
public Optional<List<ulong>> ExemptChannels { internal get; set; }
}
}
diff --git a/DisCatSharp/Net/Models/BaseEditModel.cs b/DisCatSharp/Net/Models/BaseEditModel.cs
index 0e6a39487..77ef325b9 100644
--- a/DisCatSharp/Net/Models/BaseEditModel.cs
+++ b/DisCatSharp/Net/Models/BaseEditModel.cs
@@ -1,34 +1,34 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net.Models;
/// <summary>
/// Represents the base edit model.
/// </summary>
public class BaseEditModel
{
/// <summary>
/// Reason given in audit logs
/// </summary>
public string AuditLogReason { internal get; set; }
}
diff --git a/DisCatSharp/Net/Models/ChannelEditModel.cs b/DisCatSharp/Net/Models/ChannelEditModel.cs
index 4eb2e6ced..98777b877 100644
--- a/DisCatSharp/Net/Models/ChannelEditModel.cs
+++ b/DisCatSharp/Net/Models/ChannelEditModel.cs
@@ -1,112 +1,112 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
namespace DisCatSharp.Net.Models;
/// <summary>
/// Represents a channel edit model.
/// </summary>
public class ChannelEditModel : BaseEditModel
{
/// <summary>
/// Sets the channel's new name.
/// </summary>
public string Name { internal get; set; }
/// <summary>
/// Sets the channel's type.
/// This can only be used to convert between text and news channels.
/// </summary>
public Optional<ChannelType> Type { internal get; set; }
/// <summary>
/// Sets the channel's new position.
/// </summary>
public int? Position { internal get; set; }
/// <summary>
/// Sets the channel's new topic.
/// </summary>
public Optional<string> Topic { internal get; set; }
/// <summary>
/// Sets whether the channel is to be marked as NSFW.
/// </summary>
public bool? Nsfw { internal get; set; }
/// <summary>
/// <para>Sets the parent of this channel.</para>
/// <para>This should be channel with <see cref="DisCatSharp.Entities.DiscordChannel.Type"/> set to <see cref="ChannelType.Category"/>.</para>
/// </summary>
public Optional<DiscordChannel> Parent { internal get; set; }
/// <summary>
/// Sets the voice channel's new bitrate.
/// </summary>
public int? Bitrate { internal get; set; }
/// <summary>
/// <para>Sets the voice channel's new user limit.</para>
/// <para>Setting this to 0 will disable the user limit.</para>
/// </summary>
public int? UserLimit { internal get; set; }
/// <summary>
/// <para>Sets the channel's new slow mode timeout.</para>
/// <para>Setting this to null or 0 will disable slow mode.</para>
/// </summary>
public Optional<int?> PerUserRateLimit { internal get; set; }
/// <summary>
/// <para>Sets the voice channel's region override.</para>
/// <para>Setting this to null will set it to automatic.</para>
/// </summary>
public Optional<DiscordVoiceRegion> RtcRegion { internal get; set; }
/// <summary>
/// <para>Sets the voice channel's video quality.</para>
/// </summary>
public VideoQualityMode? QualityMode { internal get; set; }
/// <summary>
/// Sets this channel's default duration for newly created threads, in minutes, to automatically archive the thread after recent activity.
/// </summary>
public ThreadAutoArchiveDuration? DefaultAutoArchiveDuration { internal get; set; }
/// <summary>
/// Sets the channel's permission overwrites.
/// </summary>
public IEnumerable<DiscordOverwriteBuilder> PermissionOverwrites { internal get; set; }
public Optional<ChannelFlags?> Flags { internal get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="ChannelEditModel"/> class.
/// </summary>
internal ChannelEditModel()
{ }
}
diff --git a/DisCatSharp/Net/Models/ForumChannelEditModel.cs b/DisCatSharp/Net/Models/ForumChannelEditModel.cs
index eaac9d607..8b0146d99 100644
--- a/DisCatSharp/Net/Models/ForumChannelEditModel.cs
+++ b/DisCatSharp/Net/Models/ForumChannelEditModel.cs
@@ -1,119 +1,119 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
namespace DisCatSharp.Net.Models;
/// <summary>
/// Represents a forum channel edit model.
/// </summary>
public class ForumChannelEditModel : BaseEditModel
{
/// <summary>
/// Sets the channel's new name.
/// </summary>
public string Name { internal get; set; }
/// <summary>
/// Sets the channel's new position.
/// </summary>
public int? Position { internal get; set; }
/// <summary>
/// Sets the channel's new topic.
/// </summary>
public Optional<string> Topic { internal get; set; }
/// <summary>
/// Sets the channel's new template.
/// <note type="warning">This is not yet released and won't be applied by library.</note>
/// </summary>
public Optional<string> Template { internal get; set; }
/// <summary>
/// Sets whether the channel is to be marked as NSFW.
/// </summary>
public bool? Nsfw { internal get; set; }
/// <summary>
/// Sets the available tags.
/// </summary>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public Optional<List<ForumPostTag>?> AvailableTags { internal get; set; }
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
/// <summary>
/// Sets the default reaction emoji.
/// </summary>
public Optional<ForumReactionEmoji> DefaultReactionEmoji { internal get; set; }
/// <summary>
/// Sets the default forum post sort order
/// </summary>
public Optional<ForumPostSortOrder?> DefaultSortOrder { internal get; set; }
/// <summary>
/// <para>Sets the parent of this channel.</para>
/// <para>This should be channel with <see cref="DisCatSharp.Entities.DiscordChannel.Type"/> set to <see cref="ChannelType.Category"/>.</para>
/// </summary>
public Optional<DiscordChannel> Parent { internal get; set; }
/// <summary>
/// <para>Sets the voice channel's new user limit.</para>
/// <para>Setting this to 0 will disable the user limit.</para>
/// </summary>
public int? UserLimit { internal get; set; }
/// <summary>
/// <para>Sets the channel's new slow mode timeout.</para>
/// <para>Setting this to null or 0 will disable slow mode.</para>
/// </summary>
public Optional<int?> PerUserRateLimit { internal get; set; }
/// <summary>
/// <para>Sets the channel's new post slow mode timeout.</para>
/// <para>Setting this to null or 0 will disable slow mode.</para>
/// </summary>
public Optional<int?> PostCreateUserRateLimit { internal get; set; }
/// <summary>
/// Sets this channel's default duration for newly created threads, in minutes, to automatically archive the thread after recent activity.
/// </summary>
public Optional<ThreadAutoArchiveDuration?> DefaultAutoArchiveDuration { internal get; set; }
/// <summary>
/// Sets the channel's permission overwrites.
/// </summary>
public IEnumerable<DiscordOverwriteBuilder> PermissionOverwrites { internal get; set; }
public Optional<ChannelFlags?> Flags { internal get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="ChannelEditModel"/> class.
/// </summary>
internal ForumChannelEditModel()
{ }
}
diff --git a/DisCatSharp/Net/Models/ForumPostTagEditModel.cs b/DisCatSharp/Net/Models/ForumPostTagEditModel.cs
index 7ee6f8157..2bdd59aa6 100644
--- a/DisCatSharp/Net/Models/ForumPostTagEditModel.cs
+++ b/DisCatSharp/Net/Models/ForumPostTagEditModel.cs
@@ -1,46 +1,46 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
namespace DisCatSharp.Net.Models;
/// <summary>
/// Represents a tag edit model.
/// </summary>
public class ForumPostTagEditModel : BaseEditModel
{
/// <summary>
/// Sets the tags's new name.
/// </summary>
public Optional<string> Name { internal get; set; }
/// <summary>
/// Sets the tags's new emoji.
/// </summary>
public Optional<DiscordEmoji> Emoji { internal get; set; }
/// <summary>
/// Sets whether the tag should be mod only.
/// </summary>
public Optional<bool> Moderated { internal get; set; }
}
diff --git a/DisCatSharp/Net/Models/GuildEditModel.cs b/DisCatSharp/Net/Models/GuildEditModel.cs
index ee5067d3e..098565ae7 100644
--- a/DisCatSharp/Net/Models/GuildEditModel.cs
+++ b/DisCatSharp/Net/Models/GuildEditModel.cs
@@ -1,135 +1,135 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.IO;
using DisCatSharp.Entities;
using DisCatSharp.Enums;
namespace DisCatSharp.Net.Models;
/// <summary>
/// Represents a guild edit model.
/// </summary>
public class GuildEditModel : BaseEditModel
{
/// <summary>
/// The new guild name.
/// </summary>
public Optional<string> Name { internal get; set; }
/// <summary>
/// The new guild icon.
/// </summary>
public Optional<Stream> Icon { internal get; set; }
/// <summary>
/// The new guild verification level.
/// </summary>
public Optional<VerificationLevel> VerificationLevel { internal get; set; }
/// <summary>
/// The new guild default message notification level.
/// </summary>
public Optional<DefaultMessageNotifications> DefaultMessageNotifications { internal get; set; }
/// <summary>
/// The new guild MFA level.
/// </summary>
public Optional<MfaLevel> MfaLevel { internal get; set; }
/// <summary>
/// The new guild explicit content filter level.
/// </summary>
public Optional<ExplicitContentFilter> ExplicitContentFilter { internal get; set; }
/// <summary>
/// The new AFK voice channel.
/// </summary>
public Optional<DiscordChannel> AfkChannel { internal get; set; }
/// <summary>
/// The new AFK timeout time in seconds.
/// </summary>
public Optional<int> AfkTimeout { internal get; set; }
/// <summary>
/// The new guild owner.
/// </summary>
public Optional<DiscordMember> Owner { internal get; set; }
/// <summary>
/// The new guild splash.
/// </summary>
public Optional<Stream> Splash { internal get; set; }
/// <summary>
/// The new guild system channel.
/// </summary>
public Optional<DiscordChannel> SystemChannel { internal get; set; }
/// <summary>
/// The guild system channel flags.
/// </summary>
public Optional<SystemChannelFlags> SystemChannelFlags { internal get; set; }
/// <summary>
/// The guild description.
/// </summary>
public Optional<string> Description { internal get; set; }
/// <summary>
/// The new guild rules channel.
/// </summary>
public Optional<DiscordChannel> RulesChannel { internal get; set; }
/// <summary>
/// The new guild public updates channel.
/// </summary>
public Optional<DiscordChannel> PublicUpdatesChannel { internal get; set; }
/// <summary>
/// The new guild preferred locale.
/// </summary>
public Optional<string> PreferredLocale { internal get; set; }
/// <summary>
/// The new banner of the guild
/// </summary>
public Optional<Stream> Banner { get; set; }
/// <summary>
/// The new discovery splash image of the guild
/// </summary>
public Optional<Stream> DiscoverySplash { get; set; }
/// <summary>
/// Whether the premium progress bar should be enabled
/// </summary>
public Optional<bool> PremiumProgressBarEnabled { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="GuildEditModel"/> class.
/// </summary>
internal GuildEditModel()
{ }
}
diff --git a/DisCatSharp/Net/Models/MemberEditModel.cs b/DisCatSharp/Net/Models/MemberEditModel.cs
index c1ed2e40e..9478cbc02 100644
--- a/DisCatSharp/Net/Models/MemberEditModel.cs
+++ b/DisCatSharp/Net/Models/MemberEditModel.cs
@@ -1,64 +1,64 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net.Models;
/// <summary>
/// Represents a member edit model.
/// </summary>
public class MemberEditModel : BaseEditModel
{
/// <summary>
/// New nickname
/// </summary>
public Optional<string> Nickname { internal get; set; }
/// <summary>
/// New roles
/// </summary>
public Optional<List<DiscordRole>> Roles { internal get; set; }
/// <summary>
/// Whether this user should be muted
/// </summary>
public Optional<bool> Muted { internal get; set; }
/// <summary>
/// Whether this user should be deafened
/// </summary>
public Optional<bool> Deafened { internal get; set; }
/// <summary>
/// Voice channel to move this user to, set to null to kick
/// </summary>
public Optional<DiscordChannel> VoiceChannel { internal get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="MemberEditModel"/> class.
/// </summary>
internal MemberEditModel()
{ }
}
diff --git a/DisCatSharp/Net/Models/MembershipScreeningEditModel.cs b/DisCatSharp/Net/Models/MembershipScreeningEditModel.cs
index 3defb05d4..104c437fd 100644
--- a/DisCatSharp/Net/Models/MembershipScreeningEditModel.cs
+++ b/DisCatSharp/Net/Models/MembershipScreeningEditModel.cs
@@ -1,51 +1,51 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
namespace DisCatSharp.Net.Models;
/// <summary>
/// Represents a membership screening edit model.
/// </summary>
public class MembershipScreeningEditModel : BaseEditModel
{
/// <summary>
/// Sets whether membership screening should be enabled for this guild
/// </summary>
public Optional<bool> Enabled { internal get; set; }
/// <summary>
/// Sets the server description shown in the membership screening form
/// </summary>
public Optional<string> Description { internal get; set; }
/// <summary>
/// Sets the fields in this membership screening form
/// </summary>
public Optional<DiscordGuildMembershipScreeningField[]> Fields { internal get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="MembershipScreeningEditModel"/> class.
/// </summary>
internal MembershipScreeningEditModel() { }
}
diff --git a/DisCatSharp/Net/Models/RoleEditModel.cs b/DisCatSharp/Net/Models/RoleEditModel.cs
index d497f2a27..81676d731 100644
--- a/DisCatSharp/Net/Models/RoleEditModel.cs
+++ b/DisCatSharp/Net/Models/RoleEditModel.cs
@@ -1,83 +1,83 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.IO;
using DisCatSharp.Entities;
using DisCatSharp.Enums;
namespace DisCatSharp.Net.Models;
/// <summary>
/// Represents a role edit model.
/// </summary>
public class RoleEditModel : BaseEditModel
{
/// <summary>
/// New role name
/// </summary>
public string Name { internal get; set; }
/// <summary>
/// New role permissions
/// </summary>
public Permissions? Permissions { internal get; set; }
/// <summary>
/// New role color
/// </summary>
public DiscordColor? Color { internal get; set; }
/// <summary>
/// Whether new role should be hoisted (Shown in the sidebar)
/// </summary>
public bool? Hoist { internal get; set; }
/// <summary>
/// Whether new role should be mentionable
/// </summary>
public bool? Mentionable { internal get; set; }
/// <summary>
/// The new role icon.
/// </summary>
public Optional<Stream> Icon { internal get; set; }
/// <summary>
/// The new role icon from unicode emoji.
/// </summary>
public Optional<DiscordEmoji> UnicodeEmoji { internal get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="RoleEditModel"/> class.
/// </summary>
internal RoleEditModel()
{
this.Name = null;
this.Permissions = null;
this.Color = null;
this.Hoist = null;
this.Mentionable = null;
this.Icon = null;
this.UnicodeEmoji = null;
}
}
diff --git a/DisCatSharp/Net/Models/ScheduledEventEditModel.cs b/DisCatSharp/Net/Models/ScheduledEventEditModel.cs
index 1d92ef40e..4bd2cd081 100644
--- a/DisCatSharp/Net/Models/ScheduledEventEditModel.cs
+++ b/DisCatSharp/Net/Models/ScheduledEventEditModel.cs
@@ -1,85 +1,85 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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 DisCatSharp.Entities;
using DisCatSharp.Enums;
namespace DisCatSharp.Net.Models;
/// <summary>
/// Represents a scheduled event edit model.
/// </summary>
public class ScheduledEventEditModel : BaseEditModel
{
/// <summary>
/// Gets or sets the channel.
/// </summary>
public Optional<DiscordChannel> Channel { get; set; }
/// <summary>
/// Gets or sets the location.
/// </summary>
public Optional<string> Location { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public Optional<string> Name { get; set; }
/// <summary>
/// Gets or sets the description.
/// </summary>
public Optional<string> Description { get; set; }
/// <summary>
/// Gets or sets the time to schedule the scheduled event.
/// </summary>
public Optional<DateTimeOffset> ScheduledStartTime { get; internal set; }
/// <summary>
/// Gets or sets the time when the scheduled event is scheduled to end.
/// </summary>
public Optional<DateTimeOffset> ScheduledEndTime { get; internal set; }
/// <summary>
/// Gets or sets the entity type of the scheduled event.
/// </summary>
public Optional<ScheduledEventEntityType> EntityType { get; set; }
/// <summary>
/// Gets or sets the cover image as base64.
/// </summary>
public Optional<Stream> CoverImage { get; set; }
/// <summary>
/// Gets or sets the status of the scheduled event.
/// </summary>
public Optional<ScheduledEventStatus> Status { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="ScheduledEventEditModel"/> class.
/// </summary>
internal ScheduledEventEditModel() { }
}
diff --git a/DisCatSharp/Net/Models/ThreadEditModel.cs b/DisCatSharp/Net/Models/ThreadEditModel.cs
index 1911d2051..fc7d49310 100644
--- a/DisCatSharp/Net/Models/ThreadEditModel.cs
+++ b/DisCatSharp/Net/Models/ThreadEditModel.cs
@@ -1,81 +1,81 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
namespace DisCatSharp.Net.Models;
/// <summary>
/// Represents a thread edit model.
/// </summary>
public class ThreadEditModel : BaseEditModel
{
/// <summary>
/// Sets the thread's new name.
/// </summary>
public string Name { internal get; set; }
/// <summary>
/// Sets the thread's locked state.
/// </summary>
public Optional<bool?> Locked { internal get; set; }
/// <summary>
/// Sets the thread's archived state.
/// </summary>
public Optional<bool?> Archived { internal get; set; }
/// <summary>
/// Sets the thread's auto archive duration.
/// </summary>
public Optional<ThreadAutoArchiveDuration?> AutoArchiveDuration { internal get; set; }
/// <summary>
/// Sets the thread's new user rate limit.
/// </summary>
public Optional<int?> PerUserRateLimit { internal get; set; }
/// <summary>
/// Sets the thread's invitable state.
/// </summary>
public Optional<bool?> Invitable { internal get; set; }
/// <summary>
/// Whether the thread is pinned within the <see cref="ChannelType.Forum">forum</see> channel.
/// </summary>
public Optional<bool?> Pinned { internal get; set; }
/// <summary>
/// Sets the thread's applied tags.
/// </summary>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public Optional<IEnumerable<ForumPostTag>?> AppliedTags { internal get; set; }
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
/// <summary>
/// Initializes a new instance of the <see cref="ThreadEditModel"/> class.
/// </summary>
internal ThreadEditModel() { }
}
diff --git a/DisCatSharp/Net/Models/WelcomeScreenEditModel.cs b/DisCatSharp/Net/Models/WelcomeScreenEditModel.cs
index cac4e2e6d..24c82611d 100644
--- a/DisCatSharp/Net/Models/WelcomeScreenEditModel.cs
+++ b/DisCatSharp/Net/Models/WelcomeScreenEditModel.cs
@@ -1,48 +1,48 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net.Models;
/// <summary>
/// Represents a welcome screen edit model.
/// </summary>
public class WelcomeScreenEditModel
{
/// <summary>
/// Sets whether the welcome screen should be enabled.
/// </summary>
public Optional<bool> Enabled { internal get; set; }
/// <summary>
/// Sets the welcome channels.
/// </summary>
public Optional<IEnumerable<DiscordGuildWelcomeScreenChannel>> WelcomeChannels { internal get; set; }
/// <summary>
/// Sets the serer description shown.
/// </summary>
public Optional<string> Description { internal get; set; }
}
diff --git a/DisCatSharp/Net/Rest/BaseRestRequest.cs b/DisCatSharp/Net/Rest/BaseRestRequest.cs
index 67bc93493..801f0163e 100644
--- a/DisCatSharp/Net/Rest/BaseRestRequest.cs
+++ b/DisCatSharp/Net/Rest/BaseRestRequest.cs
@@ -1,131 +1,131 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
namespace DisCatSharp.Net;
/// <summary>
/// Represents a request sent over HTTP.
/// </summary>
public abstract class BaseRestRequest
{
/// <summary>
/// Gets the discord client.
/// </summary>
protected internal BaseDiscordClient Discord { get; }
/// <summary>
/// Gets the request task source.
/// </summary>
protected internal TaskCompletionSource<RestResponse> RequestTaskSource { get; }
/// <summary>
/// Gets the url to which this request is going to be made.
/// </summary>
public Uri Url { get; }
/// <summary>
/// Gets the HTTP method used for this request.
/// </summary>
public RestRequestMethod Method { get; }
/// <summary>
/// Gets the generic path (no parameters) for this request.
/// </summary>
public string Route { get; }
/// <summary>
/// Gets the headers sent with this request.
/// </summary>
public IReadOnlyDictionary<string, string> Headers { get; }
/// <summary>
/// Gets the override for the rate limit bucket wait time.
/// </summary>
public double? RateLimitWaitOverride { get; }
/// <summary>
/// Gets the rate limit bucket this request is in.
/// </summary>
internal RateLimitBucket RateLimitBucket { get; }
/// <summary>
/// Creates a new <see cref="BaseRestRequest"/> with specified parameters.
/// </summary>
/// <param name="client"><see cref="DiscordClient"/> from which this request originated.</param>
/// <param name="bucket">Rate limit bucket to place this request in.</param>
/// <param name="url">Uri to which this request is going to be sent to.</param>
/// <param name="method">Method to use for this request,</param>
/// <param name="route">The generic route the request url will use.</param>
/// <param name="headers">Additional headers for this request.</param>
/// <param name="ratelimitWaitOverride">Override for ratelimit bucket wait time.</param>
internal BaseRestRequest(BaseDiscordClient client, RateLimitBucket bucket, Uri url, RestRequestMethod method, string route, IReadOnlyDictionary<string, string> headers = null, double? ratelimitWaitOverride = null)
{
this.Discord = client;
this.RateLimitBucket = bucket;
this.RequestTaskSource = new TaskCompletionSource<RestResponse>();
this.Url = url;
this.Method = method;
this.Route = route;
this.RateLimitWaitOverride = ratelimitWaitOverride;
if (headers != null)
{
headers = headers.Select(x => new KeyValuePair<string, string>(x.Key, Uri.EscapeDataString(x.Value)))
.ToDictionary(x => x.Key, x => x.Value);
this.Headers = headers;
}
}
/// <summary>
/// Asynchronously waits for this request to complete.
/// </summary>
/// <returns>HTTP response to this request.</returns>
public Task<RestResponse> WaitForCompletionAsync()
=> this.RequestTaskSource.Task;
/// <summary>
/// Sets as completed.
/// </summary>
/// <param name="response">The response to set.</param>
protected internal void SetCompleted(RestResponse response)
=> this.RequestTaskSource.SetResult(response);
/// <summary>
/// Sets as faulted.
/// </summary>
/// <param name="ex">The exception to set.</param>
protected internal void SetFaulted(Exception ex)
=> this.RequestTaskSource.SetException(ex);
/// <summary>
/// Tries to set as faulted.
/// </summary>
/// <param name="ex">The exception to set.</param>
protected internal bool TrySetFaulted(Exception ex)
=> this.RequestTaskSource.TrySetException(ex);
}
diff --git a/DisCatSharp/Net/Rest/DiscordApiClient.cs b/DisCatSharp/Net/Rest/DiscordApiClient.cs
index 4fc79bd32..4ca888196 100644
--- a/DisCatSharp/Net/Rest/DiscordApiClient.cs
+++ b/DisCatSharp/Net/Rest/DiscordApiClient.cs
@@ -1,5812 +1,5811 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 AITSYS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using DisCatSharp.Entities;
using DisCatSharp.Enums;
using DisCatSharp.Net.Abstractions;
using DisCatSharp.Net.Serialization;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
-using static System.Net.Mime.MediaTypeNames;
-
namespace DisCatSharp.Net;
/// <summary>
/// Represents a discord api client.
/// </summary>
public sealed class DiscordApiClient
{
/// <summary>
/// The audit log reason header name.
/// </summary>
private const string REASON_HEADER_NAME = "X-Audit-Log-Reason";
/// <summary>
/// Gets the discord client.
/// </summary>
internal BaseDiscordClient Discord { get; }
/// <summary>
/// Gets the rest client.
/// </summary>
internal RestClient Rest { get; }
/// <summary>
/// Initializes a new instance of the <see cref="DiscordApiClient"/> class.
/// </summary>
/// <param name="client">The client.</param>
internal DiscordApiClient(BaseDiscordClient client)
{
this.Discord = client;
this.Rest = new RestClient(client);
}
/// <summary>
/// Initializes a new instance of the <see cref="DiscordApiClient"/> class.
/// </summary>
/// <param name="proxy">The proxy.</param>
/// <param name="timeout">The timeout.</param>
/// <param name="useRelativeRateLimit">If true, use relative rate limit.</param>
/// <param name="logger">The logger.</param>
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);
}
/// <summary>
/// Builds the query string.
/// </summary>
/// <param name="values">The values.</param>
/// <param name="post">Whether this query will be transmitted via POST.</param>
private static string BuildQueryString(IDictionary<string, string> values, bool post = false)
{
if (values == null || values.Count == 0)
return string.Empty;
var valsCollection = values.Select(xkvp =>
$"{WebUtility.UrlEncode(xkvp.Key)}={WebUtility.UrlEncode(xkvp.Value)}");
var vals = string.Join("&", valsCollection);
return !post ? $"?{vals}" : vals;
}
/// <summary>
/// Prepares the message.
/// </summary>
/// <param name="msgRaw">The msg_raw.</param>
/// <returns>A DiscordMessage.</returns>
private DiscordMessage PrepareMessage(JToken msgRaw)
{
var author = msgRaw["author"].ToObject<TransportUser>();
var ret = msgRaw.ToDiscordObject<DiscordMessage>();
ret.Discord = this.Discord;
this.PopulateMessage(author, ret);
var referencedMsg = msgRaw["referenced_message"];
if (ret.MessageType == MessageType.Reply && !string.IsNullOrWhiteSpace(referencedMsg?.ToString()))
{
author = referencedMsg["author"].ToObject<TransportUser>();
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;
}
/// <summary>
/// Populates the message.
/// </summary>
/// <param name="author">The author.</param>
/// <param name="ret">The message.</param>
private void PopulateMessage(TransportUser author, DiscordMessage ret)
{
var guild = ret.Channel?.Guild;
//If this is a webhook, it shouldn't be in the user cache.
if (author.IsBot && int.Parse(author.Discriminator) == 0)
{
ret.Author = new DiscordUser(author) { Discord = this.Discord };
}
else
{
if (!this.Discord.UserCache.TryGetValue(author.Id, out var usr))
{
this.Discord.UserCache[author.Id] = usr = new DiscordUser(author) { Discord = this.Discord };
}
if (guild != null)
{
if (!guild.Members.TryGetValue(author.Id, out var mbr))
mbr = new DiscordMember(usr) { Discord = this.Discord, GuildId = guild.Id };
ret.Author = mbr;
}
else
{
ret.Author = usr;
}
}
ret.PopulateMentions();
ret.ReactionsInternal ??= new List<DiscordReaction>();
foreach (var xr in ret.ReactionsInternal)
xr.Emoji.Discord = this.Discord;
}
/// <summary>
/// Executes a rest request.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="bucket">The bucket.</param>
/// <param name="url">The url.</param>
/// <param name="method">The method.</param>
/// <param name="route">The route.</param>
/// <param name="headers">The headers.</param>
/// <param name="payload">The payload.</param>
/// <param name="ratelimitWaitOverride">The ratelimit wait override.</param>
internal Task<RestResponse> DoRequestAsync(BaseDiscordClient client, RateLimitBucket bucket, Uri url, RestRequestMethod method, string route, IReadOnlyDictionary<string, string> headers = null, string payload = null, double? ratelimitWaitOverride = null)
{
var req = new RestRequest(client, bucket, url, method, route, headers, payload, ratelimitWaitOverride);
if (this.Discord != null)
this.Rest.ExecuteRequestAsync(req).LogTaskFault(this.Discord.Logger, LogLevel.Error, LoggerEvents.RestError, $"Error while executing request. Url: {url.AbsoluteUri}");
else
_ = this.Rest.ExecuteRequestAsync(req);
return req.WaitForCompletionAsync();
}
/// <summary>
/// Executes a multipart rest request for stickers.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="bucket">The bucket.</param>
/// <param name="url">The url.</param>
/// <param name="method">The method.</param>
/// <param name="route">The route.</param>
/// <param name="headers">The headers.</param>
/// <param name="file">The file.</param>
/// <param name="name">The sticker name.</param>
/// <param name="tags">The sticker tag.</param>
/// <param name="description">The sticker description.</param>
/// <param name="ratelimitWaitOverride">The ratelimit wait override.</param>
private Task<RestResponse> DoStickerMultipartAsync(BaseDiscordClient client, RateLimitBucket bucket, Uri url, RestRequestMethod method, string route, IReadOnlyDictionary<string, string> 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();
}
/// <summary>
/// Executes a multipart request.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="bucket">The bucket.</param>
/// <param name="url">The url.</param>
/// <param name="method">The method.</param>
/// <param name="route">The route.</param>
/// <param name="headers">The headers.</param>
/// <param name="values">The values.</param>
/// <param name="files">The files.</param>
/// <param name="ratelimitWaitOverride">The ratelimit wait override.</param>
private Task<RestResponse> DoMultipartAsync(BaseDiscordClient client, RateLimitBucket bucket, Uri url, RestRequestMethod method, string route, IReadOnlyDictionary<string, string> headers = null, IReadOnlyDictionary<string, string> values = null,
IReadOnlyCollection<DiscordMessageFile> 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
/// <summary>
/// Searches the members async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="name">The name.</param>
/// <param name="limit">The limit.</param>
internal async Task<IReadOnlyList<DiscordMember>> SearchMembersAsync(ulong guildId, string name, int? limit)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.MEMBERS}{Endpoints.SEARCH}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path);
var querydict = new Dictionary<string, string>
{
["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<IReadOnlyList<TransportMember>>();
var mbrs = new List<DiscordMember>();
foreach (var xtm in tms)
{
var usr = new DiscordUser(xtm.User) { Discord = this.Discord };
this.Discord.UserCache.AddOrUpdate(xtm.User.Id, usr, (id, old) =>
{
old.Username = usr.Username;
old.Discord = usr.Discord;
old.AvatarHash = usr.AvatarHash;
return old;
});
mbrs.Add(new DiscordMember(xtm) { Discord = this.Discord, GuildId = guildId });
}
return mbrs;
}
/// <summary>
/// Gets the guild ban async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="userId">The user_id.</param>
internal async Task<DiscordBan> GetGuildBanAsync(ulong guildId, ulong userId)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.BANS}/:user_id";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId, user_id = userId}, out var path);
var uri = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, uri, RestRequestMethod.GET, route).ConfigureAwait(false);
var json = JObject.Parse(res.Response);
var ban = json.ToObject<DiscordBan>();
return ban;
}
/// <summary>
/// Creates the guild async.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="regionId">The region_id.</param>
/// <param name="iconb64">The iconb64.</param>
/// <param name="verificationLevel">The verification_level.</param>
/// <param name="defaultMessageNotifications">The default_message_notifications.</param>
/// <param name="systemChannelFlags">The system_channel_flags.</param>
internal async Task<DiscordGuild> CreateGuildAsync(string name, string regionId, Optional<string> iconb64, VerificationLevel? verificationLevel,
DefaultMessageNotifications? defaultMessageNotifications, SystemChannelFlags? systemChannelFlags)
{
var pld = new RestGuildCreatePayload
{
Name = name,
RegionId = regionId,
DefaultMessageNotifications = defaultMessageNotifications,
VerificationLevel = verificationLevel,
IconBase64 = iconb64,
SystemChannelFlags = systemChannelFlags
};
var route = $"{Endpoints.GUILDS}";
var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, payload: DiscordJson.SerializeObject(pld)).ConfigureAwait(false);
var json = JObject.Parse(res.Response);
var rawMembers = (JArray)json["members"];
var guild = json.ToDiscordObject<DiscordGuild>();
if (this.Discord is DiscordClient dc)
await dc.OnGuildCreateEventAsync(guild, rawMembers, null).ConfigureAwait(false);
return guild;
}
/// <summary>
/// Creates the guild from template async.
/// </summary>
/// <param name="templateCode">The template_code.</param>
/// <param name="name">The name.</param>
/// <param name="iconb64">The iconb64.</param>
internal async Task<DiscordGuild> CreateGuildFromTemplateAsync(string templateCode, string name, Optional<string> iconb64)
{
var pld = new RestGuildCreateFromTemplatePayload
{
Name = name,
IconBase64 = iconb64
};
var route = $"{Endpoints.GUILDS}{Endpoints.TEMPLATES}/:template_code";
var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new {template_code = templateCode }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, payload: DiscordJson.SerializeObject(pld)).ConfigureAwait(false);
var json = JObject.Parse(res.Response);
var rawMembers = (JArray)json["members"];
var guild = json.ToDiscordObject<DiscordGuild>();
if (this.Discord is DiscordClient dc)
await dc.OnGuildCreateEventAsync(guild, rawMembers, null).ConfigureAwait(false);
return guild;
}
/// <summary>
/// Deletes the guild async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
internal async Task DeleteGuildAsync(ulong guildId)
{
var route = $"{Endpoints.GUILDS}/:guild_id";
var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new {guild_id = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route).ConfigureAwait(false);
if (this.Discord is DiscordClient dc)
{
var gld = dc.GuildsInternal[guildId];
await dc.OnGuildDeleteEventAsync(gld).ConfigureAwait(false);
}
}
/// <summary>
/// Modifies the guild.
/// </summary>
/// <param name="guildId">The guild id.</param>
/// <param name="name">The name.</param>
/// <param name="verificationLevel">The verification level.</param>
/// <param name="defaultMessageNotifications">The default message notifications.</param>
/// <param name="mfaLevel">The mfa level.</param>
/// <param name="explicitContentFilter">The explicit content filter.</param>
/// <param name="afkChannelId">The afk channel id.</param>
/// <param name="afkTimeout">The afk timeout.</param>
/// <param name="iconb64">The iconb64.</param>
/// <param name="ownerId">The owner id.</param>
/// <param name="splashb64">The splashb64.</param>
/// <param name="systemChannelId">The system channel id.</param>
/// <param name="systemChannelFlags">The system channel flags.</param>
/// <param name="publicUpdatesChannelId">The public updates channel id.</param>
/// <param name="rulesChannelId">The rules channel id.</param>
/// <param name="description">The description.</param>
/// <param name="bannerb64">The banner base64.</param>
/// <param name="discoverySplashb64">The discovery base64.</param>
/// <param name="preferredLocale">The preferred locale.</param>
/// <param name="premiumProgressBarEnabled">Whether the premium progress bar should be enabled.</param>
/// <param name="reason">The reason.</param>
internal async Task<DiscordGuild> ModifyGuildAsync(ulong guildId, Optional<string> name, Optional<VerificationLevel> verificationLevel,
Optional<DefaultMessageNotifications> defaultMessageNotifications, Optional<MfaLevel> mfaLevel,
Optional<ExplicitContentFilter> explicitContentFilter, Optional<ulong?> afkChannelId,
Optional<int> afkTimeout, Optional<string> iconb64, Optional<ulong> ownerId, Optional<string> splashb64,
Optional<ulong?> systemChannelId, Optional<SystemChannelFlags> systemChannelFlags,
Optional<ulong?> publicUpdatesChannelId, Optional<ulong?> rulesChannelId, Optional<string> description,
Optional<string> bannerb64, Optional<string> discoverySplashb64, Optional<string> preferredLocale, Optional<bool> 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 = discoverySplashb64,
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<DiscordGuild>();
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;
}
/// <summary>
/// Modifies the guild community settings.
/// </summary>
/// <param name="guildId">The guild id.</param>
/// <param name="features">The guild features.</param>
/// <param name="rulesChannelId">The rules channel id.</param>
/// <param name="publicUpdatesChannelId">The public updates channel id.</param>
/// <param name="preferredLocale">The preferred locale.</param>
/// <param name="description">The description.</param>
/// <param name="defaultMessageNotifications">The default message notifications.</param>
/// <param name="explicitContentFilter">The explicit content filter.</param>
/// <param name="verificationLevel">The verification level.</param>
/// <param name="reason">The reason.</param>
internal async Task<DiscordGuild> ModifyGuildCommunitySettingsAsync(ulong guildId, List<string> features, Optional<ulong?> rulesChannelId, Optional<ulong?> 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 = Optional.FromNullable(description),
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<DiscordGuild>();
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;
}
/// <summary>
/// Modifies the guild safety settings.
/// </summary>
/// <param name="guildId">The guild id.</param>
/// <param name="features">The guild features.</param>
/// <param name="safetyAlertsChannelId">The safety alerts channel id.</param>
/// <param name="reason">The reason.</param>
internal async Task<DiscordGuild> ModifyGuildSafetyAlertsSettingsAsync(ulong guildId, List<string> features, Optional<ulong?> safetyAlertsChannelId, string reason)
{
var pld = new RestGuildSafetyModifyPayload
{
SafetyAlertsChannelId = safetyAlertsChannelId,
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<DiscordGuild>();
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;
}
/// <summary>
/// Modifies the guild features.
/// </summary>
/// <param name="guildId">The guild id.</param>
/// <param name="features">The guild features.</param>
/// <param name="reason">The reason.</param>
/// <returns></returns>
internal async Task<DiscordGuild> ModifyGuildFeaturesAsync(ulong guildId, List<string> features, string reason)
{
var pld = new RestGuildFeatureModifyPayload
{
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<DiscordGuild>();
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;
}
/// <summary>
/// Enables the guilds mfa requirement.
/// </summary>
/// <param name="guildId">The guild id.</param>
/// <param name="reason">The reason.</param>
internal async Task EnableGuildMfaAsync(ulong guildId, string reason)
{
var pld = new RestGuildMfaLevelModifyPayload
{
Level = MfaLevel.Enabled
};
var headers = Utilities.GetBaseHeaders();
if (!string.IsNullOrWhiteSpace(reason))
headers.Add(REASON_HEADER_NAME, reason);
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.MFA}";
var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { guild_id = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, headers, DiscordJson.SerializeObject(pld)).ConfigureAwait(false);
}
/// <summary>
/// Disables the guilds mfa requirement.
/// </summary>
/// <param name="guildId">The guild id.</param>
/// <param name="reason">The reason.</param>
internal async Task DisableGuildMfaAsync(ulong guildId, string reason)
{
var pld = new RestGuildMfaLevelModifyPayload
{
Level = MfaLevel.Disabled
};
var headers = Utilities.GetBaseHeaders();
if (!string.IsNullOrWhiteSpace(reason))
headers.Add(REASON_HEADER_NAME, reason);
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.MFA}";
var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { guild_id = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.POST, route, headers, DiscordJson.SerializeObject(pld)).ConfigureAwait(false);
}
/// <summary>
/// Implements https://discord.com/developers/docs/resources/guild#get-guild-bans.
/// </summary>
internal async Task<IReadOnlyList<DiscordBan>> GetGuildBansAsync(ulong guildId, int? limit, ulong? before, ulong? after)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.BANS}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {guild_id = guildId }, out var path);
var urlParams = new Dictionary<string, string>();
if (limit != null)
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);
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 bansRaw = JsonConvert.DeserializeObject<IEnumerable<DiscordBan>>(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<DiscordBan>(new List<DiscordBan>(bansRaw));
return bans;
}
/// <summary>
/// Creates the guild ban async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="userId">The user_id.</param>
/// <param name="deleteMessageDays">The delete_message_days.</param>
/// <param name="reason">The reason.</param>
internal Task CreateGuildBanAsync(ulong guildId, ulong userId, int deleteMessageDays, string reason)
{
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<string, string>
{
["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 = 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);
}
/// <summary>
/// Removes the guild ban async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="userId">The user_id.</param>
/// <param name="reason">The reason.</param>
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 = 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);
}
/// <summary>
/// Leaves the guild async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
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 = guildId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route);
}
/// <summary>
/// Adds the guild member async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="userId">The user_id.</param>
/// <param name="accessToken">The access_token.</param>
/// <param name="nick">The nick.</param>
/// <param name="roles">The roles.</param>
/// <param name="muted">If true, muted.</param>
/// <param name="deafened">If true, deafened.</param>
internal async Task<DiscordMember> AddGuildMemberAsync(ulong guildId, ulong userId, string accessToken, string nick, IEnumerable<DiscordRole> roles, bool muted, bool deafened)
{
var pld = new RestGuildMemberAddPayload
{
AccessToken = accessToken,
Nickname = nick ?? "",
Roles = roles ?? new List<DiscordRole>(),
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 = 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<TransportMember>(res.Response);
return new DiscordMember(tm) { Discord = this.Discord, GuildId = guildId };
}
/// <summary>
/// Lists the guild members async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="limit">The limit.</param>
/// <param name="after">The after.</param>
internal async Task<IReadOnlyList<TransportMember>> ListGuildMembersAsync(ulong guildId, int? limit, ulong? after)
{
var urlParams = new Dictionary<string, string>();
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 = 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 membersRaw = JsonConvert.DeserializeObject<List<TransportMember>>(res.Response);
return new ReadOnlyCollection<TransportMember>(membersRaw);
}
/// <summary>
/// Adds the guild member role async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="userId">The user_id.</param>
/// <param name="roleId">The role_id.</param>
/// <param name="reason">The reason.</param>
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 = 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);
}
/// <summary>
/// Removes the guild member role async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="userId">The user_id.</param>
/// <param name="roleId">The role_id.</param>
/// <param name="reason">The reason.</param>
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 = 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);
}
/// <summary>
/// Modifies the guild channel position async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="pld">The pld.</param>
/// <param name="reason">The reason.</param>
internal Task ModifyGuildChannelPositionAsync(ulong guildId, IEnumerable<RestGuildChannelReorderPayload> 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 = 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));
}
/// <summary>
/// Modifies the guild channel parent async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="pld">The pld.</param>
/// <param name="reason">The reason.</param>
internal Task ModifyGuildChannelParentAsync(ulong guildId, IEnumerable<RestGuildChannelNewParentPayload> 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 = 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));
}
/// <summary>
/// Detaches the guild channel parent async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="pld">The pld.</param>
/// <param name="reason">The reason.</param>
internal Task DetachGuildChannelParentAsync(ulong guildId, IEnumerable<RestGuildChannelNoParentPayload> 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 = 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));
}
/// <summary>
/// Modifies the guild role position async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="pld">The pld.</param>
/// <param name="reason">The reason.</param>
internal Task ModifyGuildRolePositionAsync(ulong guildId, IEnumerable<RestGuildRoleReorderPayload> 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 = 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));
}
/// <summary>
/// Gets the audit logs async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="limit">The limit.</param>
/// <param name="after">The after.</param>
/// <param name="before">The before.</param>
/// <param name="responsible">The responsible.</param>
/// <param name="actionType">The action_type.</param>
internal async Task<AuditLog> GetAuditLogsAsync(ulong guildId, int limit, ulong? after, ulong? before, ulong? responsible, int? actionType)
{
var urlParams = new Dictionary<string, string>
{
["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 (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 = 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 auditLogDataRaw = JsonConvert.DeserializeObject<AuditLog>(res.Response);
return auditLogDataRaw;
}
/// <summary>
/// Gets the guild vanity url async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
internal async Task<DiscordInvite> GetGuildVanityUrlAsync(ulong guildId)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.VANITY_URL}";
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<DiscordInvite>(res.Response);
return invite;
}
/// <summary>
/// Gets the guild widget async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
internal async Task<DiscordWidget> GetGuildWidgetAsync(ulong guildId)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.WIDGET_JSON}";
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<DiscordWidget>();
ret.Discord = this.Discord;
ret.Guild = this.Discord.Guilds.ContainsKey(guildId) ? this.Discord.Guilds[guildId] : null;
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;
}
/// <summary>
/// Gets the guild widget settings async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
internal async Task<DiscordWidgetSettings> GetGuildWidgetSettingsAsync(ulong guildId)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.WIDGET}";
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<DiscordWidgetSettings>(res.Response);
ret.Guild = this.Discord.Guilds[guildId];
return ret;
}
/// <summary>
/// Modifies the guild widget settings async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="isEnabled">If true, is enabled.</param>
/// <param name="channelId">The channel id.</param>
/// <param name="reason">The reason.</param>
internal async Task<DiscordWidgetSettings> 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 = 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<DiscordWidgetSettings>(res.Response);
ret.Guild = this.Discord.Guilds[guildId];
return ret;
}
/// <summary>
/// Gets the guild templates async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
internal async Task<IReadOnlyList<DiscordGuildTemplate>> GetGuildTemplatesAsync(ulong guildId)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.TEMPLATES}";
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 templatesRaw = JsonConvert.DeserializeObject<IEnumerable<DiscordGuildTemplate>>(res.Response);
return new ReadOnlyCollection<DiscordGuildTemplate>(new List<DiscordGuildTemplate>(templatesRaw));
}
/// <summary>
/// Creates the guild template async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="name">The name.</param>
/// <param name="description">The description.</param>
internal async Task<DiscordGuildTemplate> 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 = 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<DiscordGuildTemplate>(res.Response);
return ret;
}
/// <summary>
/// Syncs the guild template async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="templateCode">The template_code.</param>
internal async Task<DiscordGuildTemplate> 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 = 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 templateRaw = JsonConvert.DeserializeObject<DiscordGuildTemplate>(res.Response);
return templateRaw;
}
/// <summary>
/// Modifies the guild template async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="templateCode">The template_code.</param>
/// <param name="name">The name.</param>
/// <param name="description">The description.</param>
internal async Task<DiscordGuildTemplate> 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 = 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 templateRaw = JsonConvert.DeserializeObject<DiscordGuildTemplate>(res.Response);
return templateRaw;
}
/// <summary>
/// Deletes the guild template async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="templateCode">The template_code.</param>
internal async Task<DiscordGuildTemplate> 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 = 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 templateRaw = JsonConvert.DeserializeObject<DiscordGuildTemplate>(res.Response);
return templateRaw;
}
/// <summary>
/// Gets the guild membership screening form async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
internal async Task<DiscordGuildMembershipScreening> GetGuildMembershipScreeningFormAsync(ulong guildId)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.MEMBER_VERIFICATION}";
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 screeningRaw = JsonConvert.DeserializeObject<DiscordGuildMembershipScreening>(res.Response);
return screeningRaw;
}
/// <summary>
/// Modifies the guild membership screening form async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="enabled">The enabled.</param>
/// <param name="fields">The fields.</param>
/// <param name="description">The description.</param>
internal async Task<DiscordGuildMembershipScreening> ModifyGuildMembershipScreeningFormAsync(ulong guildId, Optional<bool> enabled, Optional<DiscordGuildMembershipScreeningField[]> fields, Optional<string> 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 = 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 screeningRaw = JsonConvert.DeserializeObject<DiscordGuildMembershipScreening>(res.Response);
return screeningRaw;
}
/// <summary>
/// Gets the guild welcome screen async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
internal async Task<DiscordGuildWelcomeScreen> GetGuildWelcomeScreenAsync(ulong guildId)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.WELCOME_SCREEN}";
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<DiscordGuildWelcomeScreen>(res.Response);
return ret;
}
/// <summary>
/// Modifies the guild welcome screen async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="enabled">The enabled.</param>
/// <param name="welcomeChannels">The welcome channels.</param>
/// <param name="description">The description.</param>
internal async Task<DiscordGuildWelcomeScreen> ModifyGuildWelcomeScreenAsync(ulong guildId, Optional<bool> enabled, Optional<IEnumerable<DiscordGuildWelcomeScreenChannel>> welcomeChannels, Optional<string> 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 = 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<DiscordGuildWelcomeScreen>(res.Response);
return ret;
}
/// <summary>
/// Updates the current user voice state async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="channelId">The channel id.</param>
/// <param name="suppress">If true, suppress.</param>
/// <param name="requestToSpeakTimestamp">The request to speak timestamp.</param>
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 = 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));
}
/// <summary>
/// Updates the user voice state async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="userId">The user_id.</param>
/// <param name="channelId">The channel id.</param>
/// <param name="suppress">If true, suppress.</param>
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 = 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));
}
/// <summary>
/// Gets all auto mod rules for a guild.
/// </summary>
/// <param name="guildId">The guild id.</param>
/// <returns>A collection of all auto mod rules in the guild.</returns>
internal async Task<ReadOnlyCollection<AutomodRule>> GetAutomodRulesAsync(ulong guildId)
{
var route = $"{Endpoints.GUILDS}/:guild_id/auto-moderation/rules";
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<List<AutomodRule>>(res.Response);
return ret.AsReadOnly();
}
/// <summary>
/// Gets a specific auto mod rule in the guild.
/// </summary>
/// <param name="guildId">The guild id for the rule.</param>
/// <param name="ruleId">The rule id.</param>
/// <returns>The rule if one is found.</returns>
internal async Task<AutomodRule> GetAutomodRuleAsync(ulong guildId, ulong ruleId)
{
var route = $"{Endpoints.GUILDS}/:guild_id/auto-moderation/rules/:rule_id";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { guild_id = guildId, rule_id = ruleId }, 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<AutomodRule>(res.Response);
return ret;
}
/// <summary>
/// Creates an auto mod rule.
/// </summary>
/// <param name="guildId">The guild id of the rule.</param>
/// <param name="name">The name of the rule.</param>
/// <param name="eventType">The event type of the rule.</param>
/// <param name="triggerType">The trigger type.</param>
/// <param name="actions">The actions of the rule.</param>
/// <param name="triggerMetadata">The metadata of the rule.</param>
/// <param name="enabled">Whether this rule is enabled.</param>
/// <param name="exemptRoles">The exempt roles of the rule.</param>
/// <param name="exemptChannels">The exempt channels of the rule.</param>
/// <param name="reason">The reason for this addition.</param>
/// <returns>The new auto mod rule.</returns>
internal async Task<AutomodRule> CreateAutomodRuleAsync(ulong guildId, string name, AutomodEventType eventType, AutomodTriggerType triggerType, IEnumerable<AutomodAction> actions,
AutomodTriggerMetadata triggerMetadata = null, bool enabled = false, IEnumerable<ulong> exemptRoles = null, IEnumerable<ulong> exemptChannels = null, string reason = null)
{
var route = $"{Endpoints.GUILDS}/:guild_id/auto-moderation/rules";
var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, new { guild_id = guildId }, out var path);
RestAutomodRuleModifyPayload pld = new()
{
Name = name,
EventType = eventType,
TriggerType = triggerType,
Actions = actions.ToArray(),
Enabled = enabled,
TriggerMetadata = triggerMetadata ?? null
};
if (exemptChannels != null)
pld.ExemptChannels = exemptChannels.ToArray();
if (exemptRoles != null)
pld.ExemptRoles = exemptRoles.ToArray();
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, payload: DiscordJson.SerializeObject(pld)).ConfigureAwait(false);
var ret = JsonConvert.DeserializeObject<AutomodRule>(res.Response);
if (this.Discord is DiscordClient dc)
{
await dc.OnAutomodRuleCreated(ret).ConfigureAwait(false);
}
return ret;
}
/// <summary>
/// Modifies an auto mod role
/// </summary>
/// <param name="guildId">The guild id.</param>
/// <param name="ruleId">The rule id.</param>
/// <param name="name">The new name of the rule.</param>
/// <param name="eventType">The new event type of the rule.</param>
/// <param name="metadata">The new metadata of the rule.</param>
/// <param name="actions">The new actions of the rule.</param>
/// <param name="enabled">Whether this rule is enabled.</param>
/// <param name="exemptRoles">The new exempt roles of the rule.</param>
/// <param name="exemptChannels">The new exempt channels of the rule.</param>
/// <param name="reason">The reason for this modification.</param>
/// <returns>The updated automod rule</returns>
internal async Task<AutomodRule> ModifyAutomodRuleAsync(ulong guildId, ulong ruleId, Optional<string> name, Optional<AutomodEventType> eventType, Optional<AutomodTriggerMetadata> metadata, Optional<List<AutomodAction>> actions,
Optional<bool> enabled, Optional<List<ulong>> exemptRoles, Optional<List<ulong>> exemptChannels, string reason = null)
{
var pld = new RestAutomodRuleModifyPayload
{
Name = name,
EventType = eventType,
TriggerMetadata = metadata,
Enabled = enabled
};
if (actions.HasValue)
pld.Actions = actions.Value?.ToArray();
if (exemptChannels.HasValue)
pld.ExemptChannels = exemptChannels.Value?.ToArray();
if (exemptRoles.HasValue)
pld.ExemptRoles = exemptRoles.Value?.ToArray();
var headers = Utilities.GetBaseHeaders();
if (!string.IsNullOrWhiteSpace(reason))
headers.Add(REASON_HEADER_NAME, reason);
var route = $"{Endpoints.GUILDS}/:guild_id/auto-moderation/rules/:rule_id";
var bucket = this.Rest.GetBucket(RestRequestMethod.PATCH, route, new { guild_id = guildId, rule_id = ruleId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route, headers, payload: DiscordJson.SerializeObject(pld));
var ret = JsonConvert.DeserializeObject<AutomodRule>(res.Response);
if (this.Discord is DiscordClient dc)
{
await dc.OnAutomodRuleUpdated(ret).ConfigureAwait(false);
}
return ret;
}
/// <summary>
/// Deletes an auto mod rule.
/// </summary>
/// <param name="guildId">The guild id of the rule.</param>
/// <param name="ruleId">The rule id.</param>
/// <param name="reason">The reason for this deletion.</param>
/// <returns>The deleted auto mod rule.</returns>
internal async Task<AutomodRule> DeleteAutomodRuleAsync(ulong guildId, ulong ruleId, string reason = null)
{
var route = $"{Endpoints.GUILDS}/:guild_id/auto-moderation/rules/:rule_id";
var bucket = this.Rest.GetBucket(RestRequestMethod.DELETE, route, new { guild_id = guildId, rule_id = ruleId }, 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.DELETE, route, headers).ConfigureAwait(false);
var ret = JsonConvert.DeserializeObject<AutomodRule>(res.Response);
if (this.Discord is DiscordClient dc)
{
await dc.OnAutomodRuleDeleted(ret).ConfigureAwait(false);
}
return ret;
}
#endregion
#region Guild Scheduled Events
/// <summary>
/// Creates a scheduled event.
/// </summary>
internal async Task<DiscordScheduledEvent> CreateGuildScheduledEventAsync(ulong guildId, ulong? channelId, DiscordScheduledEventEntityMetadata metadata, string name, DateTimeOffset scheduledStartTime, DateTimeOffset? scheduledEndTime, string description, ScheduledEventEntityType type, Optional<string> coverb64, string reason = null)
{
var pld = new RestGuildScheduledEventCreatePayload
{
ChannelId = channelId,
EntityMetadata = metadata,
Name = name,
ScheduledStartTime = scheduledStartTime,
ScheduledEndTime = scheduledEndTime,
Description = description,
EntityType = type,
CoverBase64 = coverb64
};
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 = 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 scheduledEvent = JsonConvert.DeserializeObject<DiscordScheduledEvent>(res.Response);
var guild = this.Discord.Guilds[guildId];
scheduledEvent.Discord = this.Discord;
if (scheduledEvent.Creator != null)
scheduledEvent.Creator.Discord = this.Discord;
if (this.Discord is DiscordClient dc)
await dc.OnGuildScheduledEventCreateEventAsync(scheduledEvent, guild);
return scheduledEvent;
}
/// <summary>
/// Modifies a scheduled event.
/// </summary>
internal async Task<DiscordScheduledEvent> ModifyGuildScheduledEventAsync(ulong guildId, ulong scheduledEventId, Optional<ulong?> channelId, Optional<DiscordScheduledEventEntityMetadata> metadata, Optional<string> name, Optional<DateTimeOffset> scheduledStartTime, Optional<DateTimeOffset> scheduledEndTime, Optional<string> description, Optional<ScheduledEventEntityType> type, Optional<ScheduledEventStatus> status, Optional<string> coverb64, string reason = null)
{
var pld = new RestGuildScheduledEventModifyPayload
{
ChannelId = channelId,
EntityMetadata = metadata,
Name = name,
ScheduledStartTime = scheduledStartTime,
ScheduledEndTime = scheduledEndTime,
Description = description,
EntityType = type,
Status = status,
CoverBase64 = coverb64
};
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 = 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 scheduledEvent = JsonConvert.DeserializeObject<DiscordScheduledEvent>(res.Response);
var guild = this.Discord.Guilds[guildId];
scheduledEvent.Discord = this.Discord;
if (scheduledEvent.Creator != null)
{
scheduledEvent.Creator.Discord = this.Discord;
this.Discord.UserCache.AddOrUpdate(scheduledEvent.Creator.Id, scheduledEvent.Creator, (id, old) =>
{
old.Username = scheduledEvent.Creator.Username;
old.Discriminator = scheduledEvent.Creator.Discriminator;
old.AvatarHash = scheduledEvent.Creator.AvatarHash;
old.Flags = scheduledEvent.Creator.Flags;
return old;
});
}
if (this.Discord is DiscordClient dc)
await dc.OnGuildScheduledEventUpdateEventAsync(scheduledEvent, guild);
return scheduledEvent;
}
/// <summary>
/// Modifies a scheduled event.
/// </summary>
internal async Task<DiscordScheduledEvent> ModifyGuildScheduledEventStatusAsync(ulong guildId, ulong scheduledEventId, ScheduledEventStatus status, string reason = null)
{
var pld = new RestGuildScheduledEventModifyPayload
{
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 = 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 scheduledEvent = JsonConvert.DeserializeObject<DiscordScheduledEvent>(res.Response);
var guild = this.Discord.Guilds[guildId];
scheduledEvent.Discord = this.Discord;
if (scheduledEvent.Creator != null)
{
scheduledEvent.Creator.Discord = this.Discord;
this.Discord.UserCache.AddOrUpdate(scheduledEvent.Creator.Id, scheduledEvent.Creator, (id, old) =>
{
old.Username = scheduledEvent.Creator.Username;
old.Discriminator = scheduledEvent.Creator.Discriminator;
old.AvatarHash = scheduledEvent.Creator.AvatarHash;
old.Flags = scheduledEvent.Creator.Flags;
return old;
});
}
if (this.Discord is DiscordClient dc)
await dc.OnGuildScheduledEventUpdateEventAsync(scheduledEvent, guild);
return scheduledEvent;
}
/// <summary>
/// Gets a scheduled event.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="scheduledEventId">The event id.</param>
/// <param name="withUserCount">Whether to include user count.</param>
internal async Task<DiscordScheduledEvent> GetGuildScheduledEventAsync(ulong guildId, ulong scheduledEventId, bool? withUserCount)
{
var urlParams = new Dictionary<string, string>();
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 = 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 scheduledEvent = JsonConvert.DeserializeObject<DiscordScheduledEvent>(res.Response);
var guild = this.Discord.Guilds[guildId];
scheduledEvent.Discord = this.Discord;
if (scheduledEvent.Creator != null)
{
scheduledEvent.Creator.Discord = this.Discord;
this.Discord.UserCache.AddOrUpdate(scheduledEvent.Creator.Id, scheduledEvent.Creator, (id, old) =>
{
old.Username = scheduledEvent.Creator.Username;
old.Discriminator = scheduledEvent.Creator.Discriminator;
old.AvatarHash = scheduledEvent.Creator.AvatarHash;
old.Flags = scheduledEvent.Creator.Flags;
return old;
});
}
return scheduledEvent;
}
/// <summary>
/// Gets the guilds scheduled events.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="withUserCount">Whether to include the count of users subscribed to the scheduled event.</param>
internal async Task<IReadOnlyDictionary<ulong, DiscordScheduledEvent>> ListGuildScheduledEventsAsync(ulong guildId, bool? withUserCount)
{
var urlParams = new Dictionary<string, string>();
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 = 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<ulong, DiscordScheduledEvent>();
var eventsRaw = JsonConvert.DeserializeObject<List<DiscordScheduledEvent>>(res.Response);
var guild = this.Discord.Guilds[guildId];
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<ulong, DiscordScheduledEvent>(new Dictionary<ulong, DiscordScheduledEvent>(events));
}
/// <summary>
/// Deletes a guild scheduled event.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="scheduledEventId">The scheduled event id.</param>
/// <param name="reason">The reason.</param>
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 = 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);
}
/// <summary>
/// Gets the users who RSVP'd to a scheduled event.
/// Optional with member objects.
/// This endpoint is paginated.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="scheduledEventId">The scheduled event id.</param>
/// <param name="limit">The limit how many users to receive from the event.</param>
/// <param name="before">Get results before the given id.</param>
/// <param name="after">Get results after the given id.</param>
/// <param name="withMember">Whether to include guild member data. attaches guild_member property to the user object.</param>
internal async Task<IReadOnlyDictionary<ulong, DiscordScheduledEventUser>> GetGuildScheduledEventRspvUsersAsync(ulong guildId, ulong scheduledEventId, int? limit, ulong? before, ulong? after, bool? withMember)
{
var urlParams = new Dictionary<string, string>();
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 (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 = 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 rspvUsers = JsonConvert.DeserializeObject<IEnumerable<DiscordScheduledEventUser>>(res.Response);
Dictionary<ulong, DiscordScheduledEventUser> rspv = new();
foreach (var rspvUser in rspvUsers)
{
rspvUser.Discord = this.Discord;
rspvUser.GuildId = guildId;
rspvUser.User.Discord = this.Discord;
rspvUser.User = this.Discord.UserCache.AddOrUpdate(rspvUser.User.Id, rspvUser.User, (id, old) =>
{
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(rspvUser.User.Id, rspvUser);
}
return new ReadOnlyDictionary<ulong, DiscordScheduledEventUser>(new Dictionary<ulong, DiscordScheduledEventUser>(rspv));
}
#endregion
#region Channel
/// <summary>
/// Creates a guild channel.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="name">The name.</param>
/// <param name="type">The type.</param>
/// <param name="parent">The parent.</param>
/// <param name="topic">The topic.</param>
/// <param name="bitrate">The bitrate.</param>
/// <param name="userLimit">The user_limit.</param>
/// <param name="overwrites">The overwrites.</param>
/// <param name="nsfw">If true, nsfw.</param>
/// <param name="perUserRateLimit">The per user rate limit.</param>
/// <param name="qualityMode">The quality mode.</param>
/// <param name="defaultAutoArchiveDuration">The default auto archive duration.</param>
/// <param name="reason">The reason.</param>
#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
internal async Task<DiscordChannel> CreateGuildChannelAsync(ulong guildId, string name, ChannelType type, ulong? parent, Optional<string> topic, int? bitrate, int? userLimit, IEnumerable<DiscordOverwriteBuilder> overwrites, bool? nsfw, Optional<int?> perUserRateLimit, VideoQualityMode? qualityMode, ThreadAutoArchiveDuration? defaultAutoArchiveDuration, Optional<ChannelFlags?> flags, string reason)
#pragma warning restore CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
{
var restOverwrites = new List<DiscordRestOverwrite>();
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 = userLimit,
PermissionOverwrites = restOverwrites,
Nsfw = nsfw,
PerUserRateLimit = perUserRateLimit,
QualityMode = qualityMode,
DefaultAutoArchiveDuration = defaultAutoArchiveDuration,
Flags = flags
};
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 = 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<DiscordChannel>(res.Response);
ret.Initialize(this.Discord);
return ret;
}
/// <summary>
/// Creates a guild forum channel.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="name">The name.</param>
/// <param name="parent">The parent.</param>
/// <param name="topic">The topic.</param>
/// <param name="template">The template.</param>
/// <param name="defaultReactionEmoji">The default reaction emoji.</param>
/// <param name="permissionOverwrites">The overwrites.</param>
/// <param name="nsfw">If true, nsfw.</param>
/// <param name="perUserRateLimit">The per user rate limit.</param>
/// <param name="postCreateUserRateLimit">The per user post create rate limit.</param>
/// <param name="defaultAutoArchiveDuration">The default auto archive duration.</param>
/// <param name="reason">The reason.</param>
internal async Task<DiscordChannel> CreateForumChannelAsync(ulong guildId, string name, ulong? parent,
Optional<string> topic, Optional<string> template,
bool? nsfw, Optional<ForumReactionEmoji> defaultReactionEmoji,
#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
Optional<int?> perUserRateLimit, Optional<int?> postCreateUserRateLimit, Optional<ForumPostSortOrder> defaultSortOrder,
#pragma warning restore CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
ThreadAutoArchiveDuration? defaultAutoArchiveDuration, IEnumerable<DiscordOverwriteBuilder> permissionOverwrites, Optional<ChannelFlags?> flags, string reason)
#pragma warning restore CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
{
List<DiscordRestOverwrite> restoverwrites = null;
if (permissionOverwrites != null)
{
restoverwrites = new List<DiscordRestOverwrite>();
foreach (var ow in permissionOverwrites)
restoverwrites.Add(ow.Build());
}
var pld = new RestChannelCreatePayload
{
Name = name,
Topic = topic,
//Template = template,
Nsfw = nsfw,
Parent = parent,
PerUserRateLimit = perUserRateLimit,
PostCreateUserRateLimit = postCreateUserRateLimit,
DefaultAutoArchiveDuration = defaultAutoArchiveDuration,
DefaultReactionEmoji = defaultReactionEmoji,
PermissionOverwrites = restoverwrites,
DefaultSortOrder = defaultSortOrder,
Flags = flags,
Type = ChannelType.Forum
};
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 = 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<DiscordChannel>(res.Response);
ret.Initialize(this.Discord);
return ret;
}
/// <summary>
/// Modifies the channel async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="name">The name.</param>
/// <param name="position">The position.</param>
/// <param name="topic">The topic.</param>
/// <param name="nsfw">If true, nsfw.</param>
/// <param name="parent">The parent.</param>
/// <param name="bitrate">The bitrate.</param>
/// <param name="userLimit">The user_limit.</param>
/// <param name="perUserRateLimit">The per user rate limit.</param>
/// <param name="rtcRegion">The rtc region.</param>
/// <param name="qualityMode">The quality mode.</param>
/// <param name="autoArchiveDuration">The default auto archive duration.</param>
/// <param name="type">The type.</param>
/// <param name="permissionOverwrites">The permission overwrites.</param>
/// <param name="reason">The reason.</param>
#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
internal Task ModifyChannelAsync(ulong channelId, string name, int? position, Optional<string> topic, bool? nsfw, Optional<ulong?> parent, int? bitrate, int? userLimit, Optional<int?> perUserRateLimit, Optional<string> rtcRegion, VideoQualityMode? qualityMode, ThreadAutoArchiveDuration? autoArchiveDuration, Optional<ChannelType> type, IEnumerable<DiscordOverwriteBuilder> permissionOverwrites, Optional<ChannelFlags?> flags, string reason)
#pragma warning restore CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
{
List<DiscordRestOverwrite> restoverwrites = null;
if (permissionOverwrites != null)
{
restoverwrites = new List<DiscordRestOverwrite>();
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 = userLimit,
PerUserRateLimit = perUserRateLimit,
RtcRegion = rtcRegion,
QualityMode = qualityMode,
DefaultAutoArchiveDuration = autoArchiveDuration,
Type = type,
Flags = flags,
PermissionOverwrites = restoverwrites
};
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 = 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));
}
internal async Task<DiscordChannel> ModifyForumChannelAsync(ulong channelId, string name, int? position,
Optional<string> topic, Optional<string> template, bool? nsfw,
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
Optional<ulong?> parent, Optional<List<ForumPostTag>?> availableTags, Optional<ForumReactionEmoji> defaultReactionEmoji,
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
Optional<int?> perUserRateLimit, Optional<int?> postCreateUserRateLimit, Optional<ForumPostSortOrder?> defaultSortOrder,
Optional<ThreadAutoArchiveDuration?> defaultAutoArchiveDuration, IEnumerable<DiscordOverwriteBuilder> permissionOverwrites, Optional<ChannelFlags?> flags, string reason)
{
List<DiscordRestOverwrite> restoverwrites = null;
if (permissionOverwrites != null)
{
restoverwrites = new List<DiscordRestOverwrite>();
foreach (var ow in permissionOverwrites)
restoverwrites.Add(ow.Build());
}
var pld = new RestChannelModifyPayload
{
Name = name,
Position = position,
Topic = topic,
//Template = template,
Nsfw = nsfw,
Parent = parent,
PerUserRateLimit = perUserRateLimit,
PostCreateUserRateLimit = postCreateUserRateLimit,
DefaultAutoArchiveDuration = defaultAutoArchiveDuration,
DefaultReactionEmoji = defaultReactionEmoji,
PermissionOverwrites = restoverwrites,
DefaultSortOrder = defaultSortOrder,
Flags = flags,
AvailableTags = availableTags
};
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 = channelId }, 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 ret = JsonConvert.DeserializeObject<DiscordChannel>(res.Response);
ret.Initialize(this.Discord);
return ret;
}
/// <summary>
/// Gets the channel async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
internal async Task<DiscordChannel> GetChannelAsync(ulong channelId)
{
var route = $"{Endpoints.CHANNELS}/:channel_id";
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<DiscordChannel>(res.Response);
ret.Initialize(this.Discord);
return ret;
}
/// <summary>
/// Deletes the channel async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="reason">The reason.</param>
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 = channelId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route, headers);
}
/// <summary>
/// Gets the message async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="messageId">The message_id.</param>
internal async Task<DiscordMessage> 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 = 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;
}
/// <summary>
/// Creates the message async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="content">The content.</param>
/// <param name="embeds">The embeds.</param>
/// <param name="sticker">The sticker.</param>
/// <param name="replyMessageId">The reply message id.</param>
/// <param name="mentionReply">If true, mention reply.</param>
/// <param name="failOnInvalidReply">If true, fail on invalid reply.</param>
/// <param name="components">The components.</param>
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
internal async Task<DiscordMessage> CreateMessageAsync(ulong channelId, string content, IEnumerable<DiscordEmbed> embeds, DiscordSticker sticker, ulong? replyMessageId, bool mentionReply, bool failOnInvalidReply, ReadOnlyCollection<DiscordActionRowComponent>? components = null)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
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 && components == null)
throw new ArgumentException("You must specify message content, a sticker, components 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<ulong>() : new[] {sticker.Id},
IsTts = false,
HasEmbed = embeds?.Any() ?? false,
Embeds = embeds,
Components = components
};
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 = 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;
}
/// <summary>
/// Creates the message async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="builder">The builder.</param>
internal async Task<DiscordMessage> 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<ulong>() : new[] {builder.Sticker.Id},
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 = 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 fileId = 0;
List<DiscordAttachment> attachments = new(builder.Files.Count);
foreach (var file in builder.Files)
{
DiscordAttachment att = new()
{
Id = fileId,
Discord = this.Discord,
Description = file.Description,
FileName = file.FileName
};
attachments.Add(att);
fileId++;
}
pld.Attachments = attachments;
var values = new Dictionary<string, string>
{
["payload_json"] = DiscordJson.SerializeObject(pld)
};
var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.MESSAGES}";
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.FilesInternal.Where(x => x.ResetPositionTo.HasValue))
{
file.Stream.Position = file.ResetPositionTo.Value;
}
return ret;
}
}
/// <summary>
/// Gets the guild channels async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
internal async Task<IReadOnlyList<DiscordChannel>> GetGuildChannelsAsync(ulong guildId)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.CHANNELS}";
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 channelsRaw = JsonConvert.DeserializeObject<IEnumerable<DiscordChannel>>(res.Response).Select(xc => { xc.Discord = this.Discord; return xc; });
foreach (var ret in channelsRaw)
ret.Initialize(this.Discord);
return new ReadOnlyCollection<DiscordChannel>(new List<DiscordChannel>(channelsRaw));
}
/// <summary>
/// Creates the stage instance async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="topic">The topic.</param>
/// <param name="sendStartNotification">Whether everyone should be notified about the stage.</param>
/// <param name="scheduledEventId">The associated scheduled event id.</param>
/// <param name="reason">The reason.</param>
internal async Task<DiscordStageInstance> CreateStageInstanceAsync(ulong channelId, string topic, bool sendStartNotification, ulong? scheduledEventId = null, string reason = null)
{
var pld = new RestStageInstanceCreatePayload
{
ChannelId = channelId,
Topic = topic,
ScheduledEventId = scheduledEventId,
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<DiscordStageInstance>(res.Response);
return stageInstance;
}
/// <summary>
/// Gets the stage instance async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
internal async Task<DiscordStageInstance> GetStageInstanceAsync(ulong channelId)
{
var route = $"{Endpoints.STAGE_INSTANCES}/:channel_id";
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<DiscordStageInstance>(res.Response);
return stageInstance;
}
/// <summary>
/// Modifies the stage instance async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="topic">The topic.</param>
/// <param name="reason">The reason.</param>
internal Task ModifyStageInstanceAsync(ulong channelId, Optional<string> topic, string reason)
{
var pld = new RestStageInstanceModifyPayload
{
Topic = topic
};
var route = $"{Endpoints.STAGE_INSTANCES}/:channel_id";
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));
}
/// <summary>
/// Deletes the stage instance async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="reason">The reason.</param>
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 = 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);
}
/// <summary>
/// Gets the channel messages async.
/// </summary>
/// <param name="channelId">The channel id.</param>
/// <param name="limit">The limit.</param>
/// <param name="before">The before.</param>
/// <param name="after">The after.</param>
/// <param name="around">The around.</param>
internal async Task<IReadOnlyList<DiscordMessage>> GetChannelMessagesAsync(ulong channelId, int limit, ulong? before, ulong? after, ulong? around)
{
var urlParams = new Dictionary<string, string>();
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 = 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 msgsRaw = JArray.Parse(res.Response);
var msgs = new List<DiscordMessage>();
foreach (var xj in msgsRaw)
msgs.Add(this.PrepareMessage(xj));
return new ReadOnlyCollection<DiscordMessage>(new List<DiscordMessage>(msgs));
}
/// <summary>
/// Gets the channel message async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="messageId">The message_id.</param>
internal async Task<DiscordMessage> 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 = 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;
}
/// <summary>
/// Edits the message async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="messageId">The message_id.</param>
/// <param name="content">The content.</param>
/// <param name="embeds">The embeds.</param>
/// <param name="mentions">The mentions.</param>
/// <param name="components">The components.</param>
/// <param name="suppressEmbed">The suppress_embed.</param>
/// <param name="files">The files.</param>
/// <param name="attachments">The attachments to keep.</param>
internal async Task<DiscordMessage> EditMessageAsync(ulong channelId, ulong messageId, Optional<string> content, Optional<IEnumerable<DiscordEmbed>> embeds, Optional<IEnumerable<IMention>> mentions, IReadOnlyList<DiscordActionRowComponent> components, Optional<bool> suppressEmbed, IReadOnlyCollection<DiscordMessageFile> files, Optional<IEnumerable<DiscordAttachment>> 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.ValueOrDefault(),
HasEmbed = embeds.HasValue && (embeds.Value?.Any() ?? false),
Embeds = embeds.HasValue && (embeds.Value?.Any() ?? false) ? embeds.Value : null,
Components = components,
Flags = suppressEmbed.HasValue && (bool)suppressEmbed ? MessageFlags.SuppressedEmbeds : null,
Mentions = mentions
.Map(m => new DiscordMentions(m ?? Mentions.None, false, mentions.Value?.OfType<RepliedUserMention>().Any() ?? false))
.ValueOrDefault()
};
if (files?.Count > 0)
{
ulong fileId = 0;
List<DiscordAttachment> attachmentsNew = new();
foreach (var file in files)
{
DiscordAttachment att = new()
{
Id = fileId,
Discord = this.Discord,
Description = file.Description,
FileName = file.FileName
};
attachmentsNew.Add(att);
fileId++;
}
if (attachments.HasValue && attachments.Value.Any())
attachmentsNew.AddRange(attachments.Value);
pld.Attachments = attachmentsNew;
var values = new Dictionary<string, string>
{
["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 = 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.ValueOrDefault();
var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.MESSAGES}/:message_id";
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;
}
}
/// <summary>
/// Deletes the message async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="messageId">The message_id.</param>
/// <param name="reason">The reason.</param>
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 = 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);
}
/// <summary>
/// Deletes the messages async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="messageIds">The message_ids.</param>
/// <param name="reason">The reason.</param>
internal Task DeleteMessagesAsync(ulong channelId, IEnumerable<ulong> messageIds, string reason)
{
var pld = new RestChannelMessageBulkDeletePayload
{
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 = 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));
}
/// <summary>
/// Gets the channel invites async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
internal async Task<IReadOnlyList<DiscordInvite>> GetChannelInvitesAsync(ulong channelId)
{
var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.INVITES}";
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 invitesRaw = JsonConvert.DeserializeObject<IEnumerable<DiscordInvite>>(res.Response).Select(xi => { xi.Discord = this.Discord; return xi; });
return new ReadOnlyCollection<DiscordInvite>(new List<DiscordInvite>(invitesRaw));
}
/// <summary>
/// Creates the channel invite async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="maxAge">The max_age.</param>
/// <param name="maxUses">The max_uses.</param>
/// <param name="targetType">The target_type.</param>
/// <param name="targetApplicationId">The target_application.</param>
/// <param name="targetUser">The target_user.</param>
/// <param name="temporary">If true, temporary.</param>
/// <param name="unique">If true, unique.</param>
/// <param name="reason">The reason.</param>
internal async Task<DiscordInvite> CreateChannelInviteAsync(ulong channelId, int maxAge, int maxUses, TargetType? targetType, ulong? targetApplicationId, ulong? targetUser, bool temporary, bool unique, string reason)
{
var pld = new RestChannelInviteCreatePayload
{
MaxAge = maxAge,
MaxUses = maxUses,
TargetType = targetType,
TargetApplicationId = targetApplicationId,
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 = 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<DiscordInvite>(res.Response);
ret.Discord = this.Discord;
return ret;
}
/// <summary>
/// Deletes the channel permission async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="overwriteId">The overwrite_id.</param>
/// <param name="reason">The reason.</param>
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 = 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);
}
/// <summary>
/// Edits the channel permissions async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="overwriteId">The overwrite_id.</param>
/// <param name="allow">The allow.</param>
/// <param name="deny">The deny.</param>
/// <param name="type">The type.</param>
/// <param name="reason">The reason.</param>
internal Task EditChannelPermissionsAsync(ulong channelId, ulong overwriteId, Permissions allow, Permissions deny, string type, string reason)
{
var pld = new RestChannelPermissionEditPayload
{
Type = type,
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 = 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));
}
/// <summary>
/// Triggers the typing async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
internal Task TriggerTypingAsync(ulong channelId)
{
var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.TYPING}";
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);
}
/// <summary>
/// Gets the pinned messages async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
internal async Task<IReadOnlyList<DiscordMessage>> GetPinnedMessagesAsync(ulong channelId)
{
var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.PINS}";
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 msgsRaw = JArray.Parse(res.Response);
var msgs = new List<DiscordMessage>();
foreach (var xj in msgsRaw)
msgs.Add(this.PrepareMessage(xj));
return new ReadOnlyCollection<DiscordMessage>(new List<DiscordMessage>(msgs));
}
/// <summary>
/// Pins the message async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="messageId">The message_id.</param>
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 = 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);
}
/// <summary>
/// Unpins the message async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="messageId">The message_id.</param>
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 = 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);
}
/// <summary>
/// Adds the group dm recipient async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="userId">The user_id.</param>
/// <param name="accessToken">The access_token.</param>
/// <param name="nickname">The nickname.</param>
internal Task AddGroupDmRecipientAsync(ulong channelId, ulong userId, string accessToken, string nickname)
{
var pld = new RestChannelGroupDmRecipientAddPayload
{
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 = 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));
}
/// <summary>
/// Removes the group dm recipient async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="userId">The user_id.</param>
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 = 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);
}
/// <summary>
/// Creates the group dm async.
/// </summary>
/// <param name="accessTokens">The access_tokens.</param>
/// <param name="nicks">The nicks.</param>
internal async Task<DiscordDmChannel> CreateGroupDmAsync(IEnumerable<string> accessTokens, IDictionary<ulong, string> nicks)
{
var pld = new RestUserGroupDmCreatePayload
{
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<DiscordDmChannel>(res.Response);
ret.Discord = this.Discord;
return ret;
}
/// <summary>
/// Creates the dm async.
/// </summary>
/// <param name="recipientId">The recipient_id.</param>
internal async Task<DiscordDmChannel> CreateDmAsync(ulong recipientId)
{
var pld = new RestUserDmCreatePayload
{
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<DiscordDmChannel>(res.Response);
ret.Discord = this.Discord;
return ret;
}
/// <summary>
/// Follows the channel async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="webhookChannelId">The webhook_channel_id.</param>
internal async Task<DiscordFollowedChannel> FollowChannelAsync(ulong channelId, ulong webhookChannelId)
{
var pld = new FollowedChannelAddPayload
{
WebhookChannelId = webhookChannelId
};
var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.FOLLOWERS}";
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<DiscordFollowedChannel>(response.Response);
}
/// <summary>
/// Crossposts the message async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="messageId">The message_id.</param>
internal async Task<DiscordMessage> 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 = 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<DiscordMessage>(response.Response);
}
#endregion
#region Member
/// <summary>
/// Gets the current user async.
/// </summary>
internal Task<DiscordUser> GetCurrentUserAsync()
=> this.GetUserAsync("@me");
/// <summary>
/// Gets the user async.
/// </summary>
/// <param name="userId">The user_id.</param>
internal Task<DiscordUser> GetUserAsync(ulong userId)
=> this.GetUserAsync(userId.ToString(CultureInfo.InvariantCulture));
/// <summary>
/// Gets the user async.
/// </summary>
/// <param name="userId">The user_id.</param>
internal async Task<DiscordUser> GetUserAsync(string userId)
{
var route = $"{Endpoints.USERS}/:user_id";
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 userRaw = JsonConvert.DeserializeObject<TransportUser>(res.Response);
var duser = new DiscordUser(userRaw) { Discord = this.Discord };
if (this.Discord.Configuration.Intents.HasIntent(DiscordIntents.GuildPresences) && duser.Presence == null && this.Discord is DiscordClient dc)
dc.PresencesInternal[duser.Id] = new DiscordPresence
{
Discord = dc,
RawActivity = new TransportActivity(),
Activity = new DiscordActivity(),
Status = UserStatus.Offline,
InternalUser = new UserWithIdOnly { Id = duser.Id }
};
return duser;
}
/// <summary>
/// Gets the guild member async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="userId">The user_id.</param>
internal async Task<DiscordMember> 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 = 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<TransportMember>(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;
});
if (this.Discord.Configuration.Intents.HasIntent(DiscordIntents.GuildPresences) && usr.Presence == null && this.Discord is DiscordClient dc)
dc.PresencesInternal[usr.Id] = new DiscordPresence
{
Discord = dc,
RawActivity = new TransportActivity(),
Activity = new DiscordActivity(),
Status = UserStatus.Offline
};
return new DiscordMember(tm)
{
Discord = this.Discord,
GuildId = guildId
};
}
/// <summary>
/// Removes the guild member async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="userId">The user_id.</param>
/// <param name="reason">The reason.</param>
internal Task RemoveGuildMemberAsync(ulong guildId, ulong userId, string reason)
{
var urlParams = new Dictionary<string, string>();
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 = 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);
}
/// <summary>
/// Modifies the current user async.
/// </summary>
/// <param name="username">The username.</param>
/// <param name="base64Avatar">The base64_avatar.</param>
internal async Task<TransportUser> ModifyCurrentUserAsync(string username, Optional<string> base64Avatar)
{
var pld = new RestUserUpdateCurrentPayload
{
Username = username,
AvatarBase64 = base64Avatar.ValueOrDefault(),
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 userRaw = JsonConvert.DeserializeObject<TransportUser>(res.Response);
return userRaw;
}
/// <summary>
/// Gets the current user guilds async.
/// </summary>
/// <param name="limit">The limit.</param>
/// <param name="before">The before.</param>
/// <param name="after">The after.</param>
internal async Task<IReadOnlyList<DiscordGuild>> 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 guildsRaw = JsonConvert.DeserializeObject<IEnumerable<RestUserGuild>>(res.Response);
var glds = guildsRaw.Select(xug => (this.Discord as DiscordClient)?.GuildsInternal[xug.Id]);
return new ReadOnlyCollection<DiscordGuild>(new List<DiscordGuild>(glds));
}
else
{
return new ReadOnlyCollection<DiscordGuild>(JsonConvert.DeserializeObject<List<DiscordGuild>>(res.Response));
}
}
/// <summary>
/// Modifies the guild member async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="userId">The user_id.</param>
/// <param name="nick">The nick.</param>
/// <param name="roleIds">The role_ids.</param>
/// <param name="mute">The mute.</param>
/// <param name="deaf">The deaf.</param>
/// <param name="voiceChannelId">The voice_channel_id.</param>
/// <param name="verify">Whether to verify the member.</param>
/// <param name="flags">The member flags</param>
/// <param name="reason">The reason.</param>
internal Task ModifyGuildMemberAsync(ulong guildId, ulong userId, Optional<string> nick,
Optional<IEnumerable<ulong>> roleIds, Optional<bool> mute, Optional<bool> deaf,
Optional<ulong?> voiceChannelId, Optional<bool> verify, MemberFlags flags, string reason)
{
var headers = Utilities.GetBaseHeaders();
if (!string.IsNullOrWhiteSpace(reason))
headers[REASON_HEADER_NAME] = reason;
var pld = new RestGuildMemberModifyPayload
{
Nickname = nick,
RoleIds = roleIds,
Deafen = deaf,
Mute = mute,
- VoiceChannelId = voiceChannelId
- };
-
- pld.Flags = verify.HasValue && verify.Value
+ VoiceChannelId = voiceChannelId,
+ Flags = verify.HasValue && verify.Value
? flags | MemberFlags.BypassesVerification
: verify.HasValue && !verify.Value
? flags & ~MemberFlags.BypassesVerification
- : Optional.None;
+ : Optional.None
+ };
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.MEMBERS}/:user_id";
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));
}
/// <summary>
/// Modifies the time out of a guild member.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="userId">The user_id.</param>
/// <param name="until">Datetime offset.</param>
/// <param name="reason">The reason.</param>
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 = 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));
}
/// <summary>
/// Modifies the current member nickname async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="nick">The nick.</param>
/// <param name="reason">The reason.</param>
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 = 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
/// <summary>
/// Gets the guild roles async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
internal async Task<IReadOnlyList<DiscordRole>> GetGuildRolesAsync(ulong guildId)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.ROLES}";
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 rolesRaw = JsonConvert.DeserializeObject<IEnumerable<DiscordRole>>(res.Response).Select(xr => { xr.Discord = this.Discord; xr.GuildId = guildId; return xr; });
return new ReadOnlyCollection<DiscordRole>(new List<DiscordRole>(rolesRaw));
}
/// <summary>
/// Gets the guild async.
/// </summary>
/// <param name="guildId">The guild id.</param>
/// <param name="withCounts">If true, with_counts.</param>
internal async Task<DiscordGuild> GetGuildAsync(ulong guildId, bool? withCounts)
{
var urlParams = new Dictionary<string, string>();
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<DiscordGuild>();
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.GuildsInternal[guildRest.Id];
}
else
{
guildRest.Discord = this.Discord;
return guildRest;
}
}
/// <summary>
/// Modifies the guild role async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="roleId">The role_id.</param>
/// <param name="name">The name.</param>
/// <param name="permissions">The permissions.</param>
/// <param name="color">The color.</param>
/// <param name="hoist">If true, hoist.</param>
/// <param name="mentionable">If true, mentionable.</param>
/// <param name="iconb64">The icon.</param>
/// <param name="emoji">The unicode emoji icon.</param>
/// <param name="reason">The reason.</param>
internal async Task<DiscordRole> ModifyGuildRoleAsync(ulong guildId, ulong roleId, string name, Permissions? permissions, int? color, bool? hoist, bool? mentionable, Optional<string> iconb64, Optional<string> emoji, string reason)
{
var pld = new RestGuildRolePayload
{
Name = name,
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 = 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<DiscordRole>(res.Response);
ret.Discord = this.Discord;
ret.GuildId = guildId;
return ret;
}
/// <summary>
/// Deletes the role async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="roleId">The role_id.</param>
/// <param name="reason">The reason.</param>
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 = 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);
}
/// <summary>
/// Creates the guild role async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="name">The name.</param>
/// <param name="permissions">The permissions.</param>
/// <param name="color">The color.</param>
/// <param name="hoist">If true, hoist.</param>
/// <param name="mentionable">If true, mentionable.</param>
/// <param name="reason">The reason.</param>
internal async Task<DiscordRole> CreateGuildRoleAsync(ulong guildId, string name, Permissions? permissions, int? color, bool? hoist, bool? mentionable, string reason)
{
var pld = new RestGuildRolePayload
{
Name = name,
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 = 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<DiscordRole>(res.Response);
ret.Discord = this.Discord;
ret.GuildId = guildId;
return ret;
}
#endregion
#region Prune
/// <summary>
/// Gets the guild prune count async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="days">The days.</param>
/// <param name="includeRoles">The include_roles.</param>
internal async Task<int> GetGuildPruneCountAsync(ulong guildId, int days, IEnumerable<ulong> 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<string, string>
{
["days"] = days.ToString(CultureInfo.InvariantCulture)
};
var sb = includeRoles?.Aggregate(new StringBuilder(),
(sb, id) => sb.Append($"&include_roles={id}"))
?? new StringBuilder();
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.PRUNE}";
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<RestGuildPruneResultPayload>(res.Response);
return pruned.Pruned.Value;
}
/// <summary>
/// Begins the guild prune async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="days">The days.</param>
/// <param name="computePruneCount">If true, compute_prune_count.</param>
/// <param name="includeRoles">The include_roles.</param>
/// <param name="reason">The reason.</param>
internal async Task<int?> BeginGuildPruneAsync(ulong guildId, int days, bool computePruneCount, IEnumerable<ulong> 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<string, string>
{
["days"] = days.ToString(CultureInfo.InvariantCulture),
["compute_prune_count"] = computePruneCount.ToString()
};
var sb = includeRoles?.Aggregate(new StringBuilder(),
(sb, id) => sb.Append($"&include_roles={id}"))
?? new StringBuilder();
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 = 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<RestGuildPruneResultPayload>(res.Response);
return pruned.Pruned;
}
#endregion
#region GuildVarious
/// <summary>
/// Gets the template async.
/// </summary>
/// <param name="code">The code.</param>
internal async Task<DiscordGuildTemplate> 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 templatesRaw = JsonConvert.DeserializeObject<DiscordGuildTemplate>(res.Response);
return templatesRaw;
}
/// <summary>
/// Gets the guild integrations async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
internal async Task<IReadOnlyList<DiscordIntegration>> GetGuildIntegrationsAsync(ulong guildId)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.INTEGRATIONS}";
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 integrationsRaw = JsonConvert.DeserializeObject<IEnumerable<DiscordIntegration>>(res.Response).Select(xi => { xi.Discord = this.Discord; return xi; });
return new ReadOnlyCollection<DiscordIntegration>(new List<DiscordIntegration>(integrationsRaw));
}
/// <summary>
/// Gets the guild preview async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
internal async Task<DiscordGuildPreview> GetGuildPreviewAsync(ulong guildId)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.PREVIEW}";
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<DiscordGuildPreview>(res.Response);
ret.Discord = this.Discord;
return ret;
}
/// <summary>
/// Creates the guild integration async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="type">The type.</param>
/// <param name="id">The id.</param>
internal async Task<DiscordIntegration> 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 = 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<DiscordIntegration>(res.Response);
ret.Discord = this.Discord;
return ret;
}
/// <summary>
/// Modifies the guild integration async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="integrationId">The integration_id.</param>
/// <param name="expireBehaviour">The expire_behaviour.</param>
/// <param name="expireGracePeriod">The expire_grace_period.</param>
/// <param name="enableEmoticons">If true, enable_emoticons.</param>
internal async Task<DiscordIntegration> ModifyGuildIntegrationAsync(ulong guildId, ulong integrationId, int expireBehaviour, int expireGracePeriod, bool enableEmoticons)
{
var pld = new RestGuildIntegrationModifyPayload
{
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 = 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<DiscordIntegration>(res.Response);
ret.Discord = this.Discord;
return ret;
}
/// <summary>
/// Deletes the guild integration async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="integration">The integration.</param>
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 = 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));
}
/// <summary>
/// Syncs the guild integration async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="integrationId">The integration_id.</param>
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 = 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);
}
/// <summary>
/// Gets the guild voice regions async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
internal async Task<IReadOnlyList<DiscordVoiceRegion>> GetGuildVoiceRegionsAsync(ulong guildId)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.REGIONS}";
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 regionsRaw = JsonConvert.DeserializeObject<IEnumerable<DiscordVoiceRegion>>(res.Response);
return new ReadOnlyCollection<DiscordVoiceRegion>(new List<DiscordVoiceRegion>(regionsRaw));
}
/// <summary>
/// Gets the guild invites async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
internal async Task<IReadOnlyList<DiscordInvite>> GetGuildInvitesAsync(ulong guildId)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.INVITES}";
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 invitesRaw = JsonConvert.DeserializeObject<IEnumerable<DiscordInvite>>(res.Response).Select(xi => { xi.Discord = this.Discord; return xi; });
return new ReadOnlyCollection<DiscordInvite>(new List<DiscordInvite>(invitesRaw));
}
#endregion
#region Invite
/// <summary>
/// Gets the invite async.
/// </summary>
/// <param name="inviteCode">The invite_code.</param>
/// <param name="withCounts">If true, with_counts.</param>
/// <param name="withExpiration">If true, with_expiration.</param>
/// <param name="guildScheduledEventId">The scheduled event id to get.</param>
internal async Task<DiscordInvite> GetInviteAsync(string inviteCode, bool? withCounts, bool? withExpiration, ulong? guildScheduledEventId)
{
var urlParams = new Dictionary<string, string>();
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 = 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<DiscordInvite>(res.Response);
ret.Discord = this.Discord;
return ret;
}
/// <summary>
/// Deletes the invite async.
/// </summary>
/// <param name="inviteCode">The invite_code.</param>
/// <param name="reason">The reason.</param>
internal async Task<DiscordInvite> 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 = 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<DiscordInvite>(res.Response);
ret.Discord = this.Discord;
return ret;
}
/*
* Disabled due to API restrictions
*
* internal async Task<DiscordInvite> 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<DiscordInvite>(res.Response);
* ret.Discord = this.Discord;
*
* return ret;
* }
*/
#endregion
#region Connections
/// <summary>
/// Gets the users connections async.
/// </summary>
internal async Task<IReadOnlyList<DiscordConnection>> GetUserConnectionsAsync()
{
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 connectionsRaw = JsonConvert.DeserializeObject<IEnumerable<DiscordConnection>>(res.Response).Select(xc => { xc.Discord = this.Discord; return xc; });
return new ReadOnlyCollection<DiscordConnection>(new List<DiscordConnection>(connectionsRaw));
}
/// <summary>
/// Gets the applications role connection metadata records.
/// </summary>
/// <param name="id">The application id.</param>
/// <returns>A list of metadata records or <see langword="null"/>.</returns>s
internal async Task<IReadOnlyList<DiscordApplicationRoleConnectionMetadata>> GetRoleConnectionMetadataRecords(ulong id)
{
var route = $"{Endpoints.APPLICATIONS}/:application_id{Endpoints.ROLE_CONNECTIONS}{Endpoints.METADATA}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new { application_id = 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 metadataRaw = JsonConvert.DeserializeObject<IEnumerable<DiscordApplicationRoleConnectionMetadata>>(res.Response);
return new ReadOnlyCollection<DiscordApplicationRoleConnectionMetadata>(new List<DiscordApplicationRoleConnectionMetadata>(metadataRaw));
}
/// <summary>
/// Updates the applications role connection metadata records.
/// </summary>
/// <param name="id">The application id.</param>
/// <param name="metadataObjects">A list of metadata objects. Max 5.</param>
/// <returns>A list of the created metadata records.</returns>s
internal async Task<IReadOnlyList<DiscordApplicationRoleConnectionMetadata>> UpdateRoleConnectionMetadataRecords(ulong id, IEnumerable<DiscordApplicationRoleConnectionMetadata> metadataObjects)
{
var pld = new List<RestApplicationRoleConnectionMetadataPayload>();
foreach (var metadataObject in metadataObjects)
{
pld.Add(new RestApplicationRoleConnectionMetadataPayload
{
Type = metadataObject.Type,
Key = metadataObject.Key,
Name = metadataObject.Name,
Description = metadataObject.Description,
NameLocalizations = metadataObject.NameLocalizations?.GetKeyValuePairs(),
DescriptionLocalizations = metadataObject.DescriptionLocalizations?.GetKeyValuePairs()
});
}
var route = $"{Endpoints.APPLICATIONS}/:application_id{Endpoints.ROLE_CONNECTIONS}{Endpoints.METADATA}";
var bucket = this.Rest.GetBucket(RestRequestMethod.PUT, route, new { application_id = id }, 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<IEnumerable<DiscordApplicationRoleConnectionMetadata>>(res.Response);
return new ReadOnlyCollection<DiscordApplicationRoleConnectionMetadata>(new List<DiscordApplicationRoleConnectionMetadata>(ret));
}
#endregion
#region Voice
/// <summary>
/// Lists the voice regions async.
/// </summary>
internal async Task<IReadOnlyList<DiscordVoiceRegion>> 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<IEnumerable<DiscordVoiceRegion>>(res.Response);
return new ReadOnlyCollection<DiscordVoiceRegion>(new List<DiscordVoiceRegion>(regions));
}
#endregion
#region Webhooks
/// <summary>
/// Creates the webhook async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="name">The name.</param>
/// <param name="base64Avatar">The base64_avatar.</param>
/// <param name="reason">The reason.</param>
internal async Task<DiscordWebhook> CreateWebhookAsync(ulong channelId, string name, Optional<string> base64Avatar, string reason)
{
var pld = new RestWebhookPayload
{
Name = name,
AvatarBase64 = base64Avatar.ValueOrDefault(),
AvatarSet = base64Avatar.HasValue
};
var headers = new Dictionary<string, string>();
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 = 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<DiscordWebhook>(res.Response);
ret.Discord = this.Discord;
ret.ApiClient = this;
return ret;
}
/// <summary>
/// Gets the channel webhooks async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
internal async Task<IReadOnlyList<DiscordWebhook>> GetChannelWebhooksAsync(ulong channelId)
{
var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.WEBHOOKS}";
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 webhooksRaw = JsonConvert.DeserializeObject<IEnumerable<DiscordWebhook>>(res.Response).Select(xw => { xw.Discord = this.Discord; xw.ApiClient = this; return xw; });
return new ReadOnlyCollection<DiscordWebhook>(new List<DiscordWebhook>(webhooksRaw));
}
/// <summary>
/// Gets the guild webhooks async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
internal async Task<IReadOnlyList<DiscordWebhook>> GetGuildWebhooksAsync(ulong guildId)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.WEBHOOKS}";
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 webhooksRaw = JsonConvert.DeserializeObject<IEnumerable<DiscordWebhook>>(res.Response).Select(xw => { xw.Discord = this.Discord; xw.ApiClient = this; return xw; });
return new ReadOnlyCollection<DiscordWebhook>(new List<DiscordWebhook>(webhooksRaw));
}
/// <summary>
/// Gets the webhook async.
/// </summary>
/// <param name="webhookId">The webhook_id.</param>
internal async Task<DiscordWebhook> GetWebhookAsync(ulong webhookId)
{
var route = $"{Endpoints.WEBHOOKS}/:webhook_id";
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<DiscordWebhook>(res.Response);
ret.Discord = this.Discord;
ret.ApiClient = this;
return ret;
}
/// <summary>
/// Gets the webhook with token async.
/// </summary>
/// <param name="webhookId">The webhook_id.</param>
/// <param name="webhookToken">The webhook_token.</param>
internal async Task<DiscordWebhook> GetWebhookWithTokenAsync(ulong webhookId, string webhookToken)
{
var route = $"{Endpoints.WEBHOOKS}/:webhook_id/:webhook_token";
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<DiscordWebhook>(res.Response);
ret.Token = webhookToken;
ret.Id = webhookId;
ret.Discord = this.Discord;
ret.ApiClient = this;
return ret;
}
/// <summary>
/// Modifies the webhook async.
/// </summary>
/// <param name="webhookId">The webhook_id.</param>
/// <param name="channelId">The channel id.</param>
/// <param name="name">The name.</param>
/// <param name="base64Avatar">The base64_avatar.</param>
/// <param name="reason">The reason.</param>
internal async Task<DiscordWebhook> ModifyWebhookAsync(ulong webhookId, ulong channelId, string name, Optional<string> base64Avatar, string reason)
{
var pld = new RestWebhookPayload
{
Name = name,
AvatarBase64 = base64Avatar.ValueOrDefault(),
AvatarSet = base64Avatar.HasValue,
ChannelId = channelId
};
var headers = new Dictionary<string, string>();
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 = 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<DiscordWebhook>(res.Response);
ret.Discord = this.Discord;
ret.ApiClient = this;
return ret;
}
/// <summary>
/// Modifies the webhook async.
/// </summary>
/// <param name="webhookId">The webhook_id.</param>
/// <param name="name">The name.</param>
/// <param name="base64Avatar">The base64_avatar.</param>
/// <param name="webhookToken">The webhook_token.</param>
/// <param name="reason">The reason.</param>
internal async Task<DiscordWebhook> ModifyWebhookAsync(ulong webhookId, string name, string base64Avatar, string webhookToken, string reason)
{
var pld = new RestWebhookPayload
{
Name = name,
AvatarBase64 = base64Avatar
};
var headers = new Dictionary<string, string>();
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 = 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<DiscordWebhook>(res.Response);
ret.Discord = this.Discord;
ret.ApiClient = this;
return ret;
}
/// <summary>
/// Deletes the webhook async.
/// </summary>
/// <param name="webhookId">The webhook_id.</param>
/// <param name="reason">The reason.</param>
internal Task DeleteWebhookAsync(ulong webhookId, string reason)
{
var headers = new Dictionary<string, string>();
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 = webhookId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
return this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route, headers);
}
/// <summary>
/// Deletes the webhook async.
/// </summary>
/// <param name="webhookId">The webhook_id.</param>
/// <param name="webhookToken">The webhook_token.</param>
/// <param name="reason">The reason.</param>
internal Task DeleteWebhookAsync(ulong webhookId, string webhookToken, string reason)
{
var headers = new Dictionary<string, string>();
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 = 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);
}
/// <summary>
/// Executes the webhook async.
/// </summary>
/// <param name="webhookId">The webhook_id.</param>
/// <param name="webhookToken">The webhook_token.</param>
/// <param name="builder">The builder.</param>
/// <param name="threadId">The thread_id.</param>
internal async Task<DiscordMessage> 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<string, string>();
var pld = new RestWebhookExecutePayload
{
Content = builder.Content,
Username = builder.Username.ValueOrDefault(),
AvatarUrl = builder.AvatarUrl.ValueOrDefault(),
IsTts = builder.IsTts,
Embeds = builder.Embeds,
Components = builder.Components,
ThreadName = builder.ThreadName
};
if (builder.Mentions != null)
pld.Mentions = new DiscordMentions(builder.Mentions, builder.Mentions.Any());
if (builder.Files?.Count > 0)
{
ulong fileId = 0;
List<DiscordAttachment> attachments = new();
foreach (var file in builder.Files)
{
DiscordAttachment att = new()
{
Id = fileId,
Discord = this.Discord,
Description = file.Description,
FileName = file.FileName,
FileSize = null
};
attachments.Add(att);
fileId++;
}
pld.Attachments = attachments;
}
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 = webhookId, webhook_token = webhookToken }, out var path);
var qub = Utilities.GetApiUriBuilderFor(path, this.Discord.Configuration).AddParameter("wait", "true");
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<DiscordMessage>(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;
}
/// <summary>
/// Executes the webhook slack async.
/// </summary>
/// <param name="webhookId">The webhook_id.</param>
/// <param name="webhookToken">The webhook_token.</param>
/// <param name="jsonPayload">The json_payload.</param>
/// <param name="threadId">The thread_id.</param>
internal async Task<DiscordMessage> 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 = webhookId, webhook_token = webhookToken }, out var path);
var qub = Utilities.GetApiUriBuilderFor(path, this.Discord.Configuration).AddParameter("wait", "true");
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: jsonPayload).ConfigureAwait(false);
var ret = JsonConvert.DeserializeObject<DiscordMessage>(res.Response);
ret.Discord = this.Discord;
return ret;
}
/// <summary>
/// Executes the webhook github async.
/// </summary>
/// <param name="webhookId">The webhook_id.</param>
/// <param name="webhookToken">The webhook_token.</param>
/// <param name="jsonPayload">The json_payload.</param>
/// <param name="threadId">The thread_id.</param>
internal async Task<DiscordMessage> 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 = webhookId, webhook_token = webhookToken }, out var path);
var qub = Utilities.GetApiUriBuilderFor(path, this.Discord.Configuration).AddParameter("wait", "true");
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: jsonPayload).ConfigureAwait(false);
var ret = JsonConvert.DeserializeObject<DiscordMessage>(res.Response);
ret.Discord = this.Discord;
return ret;
}
/// <summary>
/// Edits the webhook message async.
/// </summary>
/// <param name="webhookId">The webhook_id.</param>
/// <param name="webhookToken">The webhook_token.</param>
/// <param name="messageId">The message_id.</param>
/// <param name="builder">The builder.</param>
/// <param name="threadId">The thread_id.</param>
internal async Task<DiscordMessage> 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 fileId = 0;
List<DiscordAttachment> attachments = new();
foreach (var file in builder.Files)
{
DiscordAttachment att = new()
{
Id = fileId,
Discord = this.Discord,
Description = file.Description,
FileName = file.FileName,
FileSize = null
};
attachments.Add(att);
fileId++;
}
if (builder.Attachments != null && builder.Attachments?.Count > 0)
attachments.AddRange(builder.Attachments);
pld.Attachments = attachments;
var values = new Dictionary<string, string>
{
["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 = webhookId, webhook_token = webhookToken, message_id = messageId }, out var path);
var qub = Utilities.GetApiUriBuilderFor(path, this.Discord.Configuration);
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<DiscordMessage>(res.Response);
ret.Discord = this.Discord;
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 = webhookId, webhook_token = webhookToken, message_id = messageId }, out var path);
var qub = Utilities.GetApiUriBuilderFor(path, this.Discord.Configuration);
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<DiscordMessage>(res.Response);
ret.Discord = this.Discord;
foreach (var att in ret.AttachmentsInternal)
att.Discord = this.Discord;
return ret;
}
}
/// <summary>
/// Edits the webhook message async.
/// </summary>
/// <param name="webhookId">The webhook_id.</param>
/// <param name="webhookToken">The webhook_token.</param>
/// <param name="messageId">The message_id.</param>
/// <param name="builder">The builder.</param>
/// <param name="threadId">The thread_id.</param>
internal Task<DiscordMessage> EditWebhookMessageAsync(ulong webhookId, string webhookToken, ulong messageId, DiscordWebhookBuilder builder, ulong threadId) =>
this.EditWebhookMessageAsync(webhookId, webhookToken, messageId.ToString(), builder, threadId.ToString());
/// <summary>
/// Gets the webhook message async.
/// </summary>
/// <param name="webhookId">The webhook_id.</param>
/// <param name="webhookToken">The webhook_token.</param>
/// <param name="messageId">The message_id.</param>
/// <param name="threadId">The thread_id.</param>
internal async Task<DiscordMessage> 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 = webhookId, webhook_token = webhookToken, message_id = messageId }, out var path);
var qub = Utilities.GetApiUriBuilderFor(path, this.Discord.Configuration);
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<DiscordMessage>(res.Response);
ret.Discord = this.Discord;
return ret;
}
/// <summary>
/// Gets the webhook message async.
/// </summary>
/// <param name="webhookId">The webhook_id.</param>
/// <param name="webhookToken">The webhook_token.</param>
/// <param name="messageId">The message_id.</param>
internal Task<DiscordMessage> GetWebhookMessageAsync(ulong webhookId, string webhookToken, ulong messageId) =>
this.GetWebhookMessageAsync(webhookId, webhookToken, messageId.ToString(), null);
/// <summary>
/// Gets the webhook message async.
/// </summary>
/// <param name="webhookId">The webhook_id.</param>
/// <param name="webhookToken">The webhook_token.</param>
/// <param name="messageId">The message_id.</param>
/// <param name="threadId">The thread_id.</param>
internal Task<DiscordMessage> GetWebhookMessageAsync(ulong webhookId, string webhookToken, ulong messageId, ulong threadId) =>
this.GetWebhookMessageAsync(webhookId, webhookToken, messageId.ToString(), threadId.ToString());
/// <summary>
/// Deletes the webhook message async.
/// </summary>
/// <param name="webhookId">The webhook_id.</param>
/// <param name="webhookToken">The webhook_token.</param>
/// <param name="messageId">The message_id.</param>
/// <param name="threadId">The thread_id.</param>
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 = webhookId, webhook_token = webhookToken, message_id = messageId }, out var path);
var qub = Utilities.GetApiUriBuilderFor(path, this.Discord.Configuration);
if (threadId != null)
qub.AddParameter("thread_id", threadId);
var url = qub.Build();
await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route);
}
/// <summary>
/// Deletes the webhook message async.
/// </summary>
/// <param name="webhookId">The webhook_id.</param>
/// <param name="webhookToken">The webhook_token.</param>
/// <param name="messageId">The message_id.</param>
internal Task DeleteWebhookMessageAsync(ulong webhookId, string webhookToken, ulong messageId) =>
this.DeleteWebhookMessageAsync(webhookId, webhookToken, messageId.ToString(), null);
/// <summary>
/// Deletes the webhook message async.
/// </summary>
/// <param name="webhookId">The webhook_id.</param>
/// <param name="webhookToken">The webhook_token.</param>
/// <param name="messageId">The message_id.</param>
/// <param name="threadId">The thread_id.</param>
internal Task DeleteWebhookMessageAsync(ulong webhookId, string webhookToken, ulong messageId, ulong threadId) =>
this.DeleteWebhookMessageAsync(webhookId, webhookToken, messageId.ToString(), threadId.ToString());
#endregion
#region Reactions
/// <summary>
/// Creates the reaction async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="messageId">The message_id.</param>
/// <param name="emoji">The emoji.</param>
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 = 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 : 0.26);
}
/// <summary>
/// Deletes the own reaction async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="messageId">The message_id.</param>
/// <param name="emoji">The emoji.</param>
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 = 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 : 0.26);
}
/// <summary>
/// Deletes the user reaction async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="messageId">The message_id.</param>
/// <param name="userId">The user_id.</param>
/// <param name="emoji">The emoji.</param>
/// <param name="reason">The reason.</param>
internal Task DeleteUserReactionAsync(ulong channelId, ulong messageId, ulong userId, string emoji, string reason)
{
var headers = new Dictionary<string, string>();
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 = 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 : 0.26);
}
/// <summary>
/// Gets the reactions async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="messageId">The message_id.</param>
/// <param name="emoji">The emoji.</param>
/// <param name="afterId">The after_id.</param>
/// <param name="limit">The limit.</param>
internal async Task<IReadOnlyList<DiscordUser>> GetReactionsAsync(ulong channelId, ulong messageId, string emoji, ulong? afterId = null, int limit = 25)
{
var urlParams = new Dictionary<string, string>();
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 = 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 reactersRaw = JsonConvert.DeserializeObject<IEnumerable<TransportUser>>(res.Response);
var reacters = new List<DiscordUser>();
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<DiscordUser>(new List<DiscordUser>(reacters));
}
/// <summary>
/// Deletes the all reactions async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="messageId">The message_id.</param>
/// <param name="reason">The reason.</param>
internal Task DeleteAllReactionsAsync(ulong channelId, ulong messageId, string reason)
{
var headers = new Dictionary<string, string>();
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 = 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 : 0.26);
}
/// <summary>
/// Deletes the reactions emoji async.
/// </summary>
/// <param name="channelId">The channel_id.</param>
/// <param name="messageId">The message_id.</param>
/// <param name="emoji">The emoji.</param>
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 = 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 : 0.26);
}
#endregion
#region Threads
/// <summary>
/// Creates the thread.
/// </summary>
/// <param name="channelId">The channel id to create the thread in.</param>
/// <param name="messageId">The optional message id to create the thread from.</param>
/// <param name="name">The name of the thread.</param>
/// <param name="autoArchiveDuration">The auto_archive_duration for the thread.</param>
/// <param name="type">Can be either <see cref="ChannelType.PublicThread"/> or <see cref="ChannelType.PrivateThread"/>.</param>
/// <param name="rateLimitPerUser">The rate limit per user.</param>
/// <param name="appliedTags">The tags to add on creation.</param>
/// <param name="reason">The reason.</param>
internal async Task<DiscordThreadChannel> CreateThreadAsync(ulong channelId, ulong? messageId, string name,
#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
ThreadAutoArchiveDuration? autoArchiveDuration, ChannelType? type, int? rateLimitPerUser, IEnumerable<ForumPostTag>? appliedTags = null, DiscordMessageBuilder builder = null, bool isForum = false, string reason = null)
#pragma warning restore CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
#pragma warning restore CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
{
var pld = new RestThreadChannelCreatePayload
{
Name = name,
AutoArchiveDuration = autoArchiveDuration,
PerUserRateLimit = rateLimitPerUser
};
if (isForum)
{
pld.Message = new RestChannelMessageCreatePayload
{
Content = builder.Content,
Attachments = builder.Attachments,
Components = builder.Components,
HasContent = true,
Embeds = builder.Embeds,
//Flags = builder.Flags,
//Mentions = builder.Mentions,
StickersIds = builder.Sticker != null ? new List<ulong>(1)
{
builder.Sticker.Id
} : null
};
if (appliedTags != null && appliedTags.Any())
{
List<ulong> tags = new();
foreach (var b in appliedTags)
tags.Add(b.Id.Value);
pld.AppliedTags = tags;
pld.Type = null;
}
}
else
{
pld.Type = type;
}
var headers = Utilities.GetBaseHeaders();
if (!string.IsNullOrWhiteSpace(reason))
headers.Add(REASON_HEADER_NAME, reason);
var route = $"{Endpoints.CHANNELS}/:channel_id";
if (messageId is not null)
route += $"{Endpoints.MESSAGES}/:message_id";
route += Endpoints.THREADS;
object param = messageId is null
? new {channel_id = channelId}
: new {channel_id = channelId, message_id = messageId};
var bucket = this.Rest.GetBucket(RestRequestMethod.POST, route, param, 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 threadChannel = JsonConvert.DeserializeObject<DiscordThreadChannel>(res.Response);
threadChannel.Discord = this.Discord;
return threadChannel;
}
/// <summary>
/// Gets the thread.
/// </summary>
/// <param name="threadId">The thread id.</param>
internal async Task<DiscordThreadChannel> GetThreadAsync(ulong threadId)
{
var route = $"{Endpoints.CHANNELS}/:thread_id";
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<DiscordThreadChannel>(res.Response);
ret.Discord = this.Discord;
return ret;
}
/// <summary>
/// Joins the thread.
/// </summary>
/// <param name="channelId">The channel id.</param>
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 = channelId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PUT, route);
}
/// <summary>
/// Leaves the thread.
/// </summary>
/// <param name="channelId">The channel id.</param>
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 = channelId }, out var path);
var url = Utilities.GetApiUriFor(path, this.Discord.Configuration);
await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.DELETE, route);
}
/// <summary>
/// Adds a thread member.
/// </summary>
/// <param name="channelId">The channel id to add the member to.</param>
/// <param name="userId">The user id to add.</param>
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 = 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);
}
/// <summary>
/// Gets a thread member.
/// </summary>
/// <param name="channelId">The channel id to get the member from.</param>
/// <param name="userId">The user id to get.</param>
/// <param name="withMember">Whether to include a <see cref="DiscordMember"/> object.</param>
internal async Task<DiscordThreadChannelMember> GetThreadMemberAsync(ulong channelId, ulong userId, bool withMember = false)
{
- var urlParams = new Dictionary<string, string>();
-
- urlParams["with_member"] = withMember.ToString(CultureInfo.InvariantCulture);
+ var urlParams = new Dictionary<string, string>
+ {
+ ["with_member"] = withMember.ToString(CultureInfo.InvariantCulture)
+ };
var route = $"{Endpoints.CHANNELS}/:channel_id{Endpoints.THREAD_MEMBERS}/:user_id";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {channel_id = channelId, user_id = userId }, 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 threadMember = JsonConvert.DeserializeObject<DiscordThreadChannelMember>(res.Response);
return threadMember;
}
/// <summary>
/// Removes a thread member.
/// </summary>
/// <param name="channelId">The channel id to remove the member from.</param>
/// <param name="userId">The user id to remove.</param>
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 = 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);
}
/// <summary>
/// Gets the thread members.
/// </summary>
/// <param name="threadId">The thread id.</param>
/// <param name="withMember">Whether to include a <see cref="DiscordMember"/> object.</param>
/// <param name="after">Get members after specified snowflake.</param>
/// <param name="limit">Limits the results.</param>
internal async Task<IReadOnlyList<DiscordThreadChannelMember>> GetThreadMembersAsync(ulong threadId, bool withMember = false, ulong? after = null, int? limit = null)
{
// TODO: Starting in API v11, List Thread Members will always return paginated results, regardless of whether with_member is passed or not.
- var urlParams = new Dictionary<string, string>();
-
- urlParams["with_member"] = withMember.ToString(CultureInfo.InvariantCulture);
+ var urlParams = new Dictionary<string, string>
+ {
+ ["with_member"] = withMember.ToString(CultureInfo.InvariantCulture)
+ };
if (after != null && withMember)
urlParams["after"] = after.Value.ToString(CultureInfo.InvariantCulture);
if (limit != null && limit > 0 && withMember)
urlParams["limit"] = limit.Value.ToString(CultureInfo.InvariantCulture);
var route = $"{Endpoints.CHANNELS}/:thread_id{Endpoints.THREAD_MEMBERS}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {thread_id = threadId }, 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 threadMembersRaw = JsonConvert.DeserializeObject<List<DiscordThreadChannelMember>>(res.Response);
return new ReadOnlyCollection<DiscordThreadChannelMember>(threadMembersRaw);
}
/// <summary>
/// Gets the active threads in a guild.
/// </summary>
/// <param name="guildId">The guild id.</param>
internal async Task<DiscordThreadResult> 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 = 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 threadReturn = JsonConvert.DeserializeObject<DiscordThreadResult>(res.Response);
threadReturn.Threads.ForEach(x => x.Discord = this.Discord);
return threadReturn;
}
/// <summary>
/// Gets the joined private archived threads in a channel.
/// </summary>
/// <param name="channelId">The channel id.</param>
/// <param name="before">Get threads before snowflake.</param>
/// <param name="limit">Limit the results.</param>
internal async Task<DiscordThreadResult> GetJoinedPrivateArchivedThreadsAsync(ulong channelId, ulong? before, int? limit)
{
var urlParams = new Dictionary<string, string>();
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 = 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 threadReturn = JsonConvert.DeserializeObject<DiscordThreadResult>(res.Response);
return threadReturn;
}
/// <summary>
/// Gets the public archived threads in a channel.
/// </summary>
/// <param name="channelId">The channel id.</param>
/// <param name="before">Get threads before snowflake.</param>
/// <param name="limit">Limit the results.</param>
internal async Task<DiscordThreadResult> GetPublicArchivedThreadsAsync(ulong channelId, ulong? before, int? limit)
{
var urlParams = new Dictionary<string, string>();
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 = 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 threadReturn = JsonConvert.DeserializeObject<DiscordThreadResult>(res.Response);
return threadReturn;
}
/// <summary>
/// Gets the private archived threads in a channel.
/// </summary>
/// <param name="channelId">The channel id.</param>
/// <param name="before">Get threads before snowflake.</param>
/// <param name="limit">Limit the results.</param>
internal async Task<DiscordThreadResult> GetPrivateArchivedThreadsAsync(ulong channelId, ulong? before, int? limit)
{
var urlParams = new Dictionary<string, string>();
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 = 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 threadReturn = JsonConvert.DeserializeObject<DiscordThreadResult>(res.Response);
return threadReturn;
}
/// <summary>
/// Modifies a thread.
/// </summary>
/// <param name="threadId">The thread to modify.</param>
/// <param name="parentType">The parent channels type as failback to ignore forum fields.</param>
/// <param name="name">The new name.</param>
/// <param name="locked">The new locked state.</param>
/// <param name="archived">The new archived state.</param>
/// <param name="perUserRateLimit">The new per user rate limit.</param>
/// <param name="autoArchiveDuration">The new auto archive duration.</param>
/// <param name="invitable">The new user invitable state.</param>
/// <param name="appliedTags">The tags to add on creation.</param>
/// <param name="pinned">Whether the post is pinned.</param>
/// <param name="reason">The reason for the modification.</param>
internal Task ModifyThreadAsync(ulong threadId, ChannelType parentType, string name, Optional<bool?> locked, Optional<bool?> archived, Optional<int?> perUserRateLimit, Optional<ThreadAutoArchiveDuration?> autoArchiveDuration, Optional<bool?> invitable, Optional<IEnumerable<ForumPostTag>> appliedTags, Optional<bool?> pinned, string reason)
{
var pld = new RestThreadChannelModifyPayload
{
Name = name,
Archived = archived,
AutoArchiveDuration = autoArchiveDuration,
Locked = locked,
PerUserRateLimit = perUserRateLimit,
Invitable = invitable
};
if (parentType == ChannelType.Forum)
{
if (appliedTags.HasValue && appliedTags.Value != null)
{
List<ulong> tags = new(appliedTags.Value.Count());
foreach (var b in appliedTags.Value)
tags.Add(b.Id.Value);
pld.AppliedTags = tags;
}
if (pinned.HasValue && pinned.Value.HasValue)
pld.Flags = pinned.Value.Value ? ChannelFlags.Pinned : Optional.None;
}
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 = 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));
}
#endregion
#region Emoji
/// <summary>
/// Gets the guild emojis async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
internal async Task<IReadOnlyList<DiscordGuildEmoji>> GetGuildEmojisAsync(ulong guildId)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.EMOJIS}";
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<IEnumerable<JObject>>(res.Response);
this.Discord.Guilds.TryGetValue(guildId, out var gld);
var users = new Dictionary<ulong, DiscordUser>();
var emojis = new List<DiscordGuildEmoji>();
foreach (var rawEmoji in emojisRaw)
{
var xge = rawEmoji.ToObject<DiscordGuildEmoji>();
xge.Guild = gld;
var xtu = rawEmoji["user"]?.ToObject<TransportUser>();
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<DiscordGuildEmoji>(emojis);
}
/// <summary>
/// Gets the guild emoji async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="emojiId">The emoji_id.</param>
internal async Task<DiscordGuildEmoji> 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 = 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(guildId, out var gld);
var emojiRaw = JObject.Parse(res.Response);
var emoji = emojiRaw.ToObject<DiscordGuildEmoji>();
emoji.Guild = gld;
var xtu = emojiRaw["user"]?.ToObject<TransportUser>();
if (xtu != null)
emoji.User = gld != null && gld.Members.TryGetValue(xtu.Id, out var member) ? member : new DiscordUser(xtu);
return emoji;
}
/// <summary>
/// Creates the guild emoji async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="name">The name.</param>
/// <param name="imageb64">The imageb64.</param>
/// <param name="roles">The roles.</param>
/// <param name="reason">The reason.</param>
internal async Task<DiscordGuildEmoji> CreateGuildEmojiAsync(ulong guildId, string name, string imageb64, IEnumerable<ulong> roles, string reason)
{
var pld = new RestGuildEmojiCreatePayload
{
Name = name,
ImageB64 = imageb64,
Roles = roles?.ToArray()
};
var headers = new Dictionary<string, string>();
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 = 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(guildId, out var gld);
var emojiRaw = JObject.Parse(res.Response);
var emoji = emojiRaw.ToObject<DiscordGuildEmoji>();
emoji.Guild = gld;
var xtu = emojiRaw["user"]?.ToObject<TransportUser>();
emoji.User = xtu != null
? gld != null && gld.Members.TryGetValue(xtu.Id, out var member) ? member : new DiscordUser(xtu)
: this.Discord.CurrentUser;
return emoji;
}
/// <summary>
/// Modifies the guild emoji async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="emojiId">The emoji_id.</param>
/// <param name="name">The name.</param>
/// <param name="roles">The roles.</param>
/// <param name="reason">The reason.</param>
internal async Task<DiscordGuildEmoji> ModifyGuildEmojiAsync(ulong guildId, ulong emojiId, string name, IEnumerable<ulong> roles, string reason)
{
var pld = new RestGuildEmojiModifyPayload
{
Name = name,
Roles = roles?.ToArray()
};
var headers = new Dictionary<string, string>();
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 = 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(guildId, out var gld);
var emojiRaw = JObject.Parse(res.Response);
var emoji = emojiRaw.ToObject<DiscordGuildEmoji>();
emoji.Guild = gld;
var xtu = emojiRaw["user"]?.ToObject<TransportUser>();
if (xtu != null)
emoji.User = gld != null && gld.Members.TryGetValue(xtu.Id, out var member) ? member : new DiscordUser(xtu);
return emoji;
}
/// <summary>
/// Deletes the guild emoji async.
/// </summary>
/// <param name="guildId">The guild_id.</param>
/// <param name="emojiId">The emoji_id.</param>
/// <param name="reason">The reason.</param>
internal Task DeleteGuildEmojiAsync(ulong guildId, ulong emojiId, string reason)
{
var headers = new Dictionary<string, string>();
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 = 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
/// <summary>
/// Gets a sticker.
/// </summary>
/// <param name="stickerId">The sticker id.</param>
internal async Task<DiscordSticker> GetStickerAsync(ulong stickerId)
{
var route = $"{Endpoints.STICKERS}/:sticker_id";
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<DiscordSticker>();
ret.Discord = this.Discord;
return ret;
}
/// <summary>
/// Gets the sticker packs.
/// </summary>
internal async Task<IReadOnlyList<DiscordStickerPack>> 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<DiscordStickerPack[]>();
return ret.ToList();
}
/// <summary>
/// Gets the guild stickers.
/// </summary>
/// <param name="guildId">The guild id.</param>
internal async Task<IReadOnlyList<DiscordSticker>> GetGuildStickersAsync(ulong guildId)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.STICKERS}";
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<DiscordSticker[]>();
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<TransportUser>();
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();
}
/// <summary>
/// Gets a guild sticker.
/// </summary>
/// <param name="guildId">The guild id.</param>
/// <param name="stickerId">The sticker id.</param>
internal async Task<DiscordSticker> 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 = 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<DiscordSticker>();
if (json["user"] is not null) // Null = Missing stickers perm //
{
var tsr = json["user"].ToDiscordObject<TransportUser>();
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;
}
/// <summary>
/// Creates the guild sticker.
/// </summary>
/// <param name="guildId">The guild id.</param>
/// <param name="name">The name.</param>
/// <param name="description">The description.</param>
/// <param name="tags">The tags.</param>
/// <param name="file">The file.</param>
/// <param name="reason">The reason.</param>
internal async Task<DiscordSticker> 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 = 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<DiscordSticker>();
ret.Discord = this.Discord;
return ret;
}
/// <summary>
/// Modifies the guild sticker.
/// </summary>
/// <param name="guildId">The guild id.</param>
/// <param name="stickerId">The sticker id.</param>
/// <param name="name">The name.</param>
/// <param name="description">The description.</param>
/// <param name="tags">The tags.</param>
/// <param name="reason">The reason.</param>
internal async Task<DiscordSticker> ModifyGuildStickerAsync(ulong guildId, ulong stickerId, Optional<string> name, Optional<string> description, Optional<string> tags, string reason)
{
var route = $"{Endpoints.GUILDS}/:guild_id{Endpoints.STICKERS}/:sticker_id";
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<string, string>
{
["payload_json"] = DiscordJson.SerializeObject(pld)
};
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.PATCH, route);
var ret = JObject.Parse(res.Response).ToDiscordObject<DiscordSticker>();
ret.Discord = this.Discord;
return null;
}
/// <summary>
/// Deletes the guild sticker async.
/// </summary>
/// <param name="guildId">The guild id.</param>
/// <param name="stickerId">The sticker id.</param>
/// <param name="reason">The reason.</param>
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 = 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
/// <summary>
/// Gets the global application commands.
/// </summary>
/// <param name="applicationId">The application id.</param>
/// <param name="withLocalizations">Whether to get the full localization dict.</param>
internal async Task<IReadOnlyList<DiscordApplicationCommand>> GetGlobalApplicationCommandsAsync(ulong applicationId, bool withLocalizations = false)
{
var route = $"{Endpoints.APPLICATIONS}/:application_id{Endpoints.COMMANDS}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {application_id = applicationId }, out var path);
var querydict = new Dictionary<string, string>
{
["with_localizations"] = withLocalizations.ToString().ToLower()
};
var url = Utilities.GetApiUriFor(path, BuildQueryString(querydict), this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route);
var ret = JsonConvert.DeserializeObject<IEnumerable<DiscordApplicationCommand>>(res.Response);
foreach (var app in ret)
app.Discord = this.Discord;
return ret.ToList();
}
/// <summary>
/// Bulk overwrites the global application commands.
/// </summary>
/// <param name="applicationId">The application id.</param>
/// <param name="commands">The commands.</param>
internal async Task<IReadOnlyList<DiscordApplicationCommand>> BulkOverwriteGlobalApplicationCommandsAsync(ulong applicationId, IEnumerable<DiscordApplicationCommand> commands)
{
var pld = new List<RestApplicationCommandCreatePayload>();
foreach (var command in commands)
{
pld.Add(new RestApplicationCommandCreatePayload
{
Type = command.Type,
Name = command.Name,
Description = command.Type == ApplicationCommandType.ChatInput ? command.Description : null,
Options = command.Options,
NameLocalizations = command.NameLocalizations?.GetKeyValuePairs(),
DescriptionLocalizations = command.DescriptionLocalizations?.GetKeyValuePairs(),
DefaultMemberPermission = command.DefaultMemberPermissions,
DmPermission = command.DmPermission,
Nsfw = command.IsNsfw
});
}
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 = 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<IEnumerable<DiscordApplicationCommand>>(res.Response);
foreach (var app in ret)
app.Discord = this.Discord;
return ret.ToList();
}
/// <summary>
/// Creates a global application command.
/// </summary>
/// <param name="applicationId">The applicationid.</param>
/// <param name="command">The command.</param>
internal async Task<DiscordApplicationCommand> CreateGlobalApplicationCommandAsync(ulong applicationId, DiscordApplicationCommand command)
{
var pld = new RestApplicationCommandCreatePayload
{
Type = command.Type,
Name = command.Name,
Description = command.Type == ApplicationCommandType.ChatInput ? command.Description : null,
Options = command.Options,
NameLocalizations = command.NameLocalizations.GetKeyValuePairs(),
DescriptionLocalizations = command.DescriptionLocalizations.GetKeyValuePairs(),
DefaultMemberPermission = command.DefaultMemberPermissions,
DmPermission = command.DmPermission,
Nsfw = command.IsNsfw
};
var route = $"{Endpoints.APPLICATIONS}/:application_id{Endpoints.COMMANDS}";
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<DiscordApplicationCommand>(res.Response);
ret.Discord = this.Discord;
return ret;
}
/// <summary>
/// Gets a global application command.
/// </summary>
/// <param name="applicationId">The application id.</param>
/// <param name="commandId">The command id.</param>
internal async Task<DiscordApplicationCommand> 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 = 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<DiscordApplicationCommand>(res.Response);
ret.Discord = this.Discord;
return ret;
}
/// <summary>
/// Edits a global application command.
/// </summary>
/// <param name="applicationId">The application id.</param>
/// <param name="commandId">The command id.</param>
/// <param name="name">The name.</param>
/// <param name="description">The description.</param>
/// <param name="options">The options.</param>
/// <param name="nameLocalization">The localizations of the name.</param>
/// <param name="descriptionLocalization">The localizations of the description.</param>
/// <param name="defaultMemberPermission">The default member permissions.</param>
/// <param name="dmPermission">The dm permission.</param>
/// <param name="isNsfw">Whether this command is marked as NSFW.</param>
internal async Task<DiscordApplicationCommand> EditGlobalApplicationCommandAsync(ulong applicationId, ulong commandId,
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
Optional<string> name, Optional<string> description, Optional<List<DiscordApplicationCommandOption>?> options,
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
Optional<DiscordApplicationCommandLocalization> nameLocalization, Optional<DiscordApplicationCommandLocalization> descriptionLocalization,
Optional<Permissions?> defaultMemberPermission, Optional<bool> dmPermission, Optional<bool> isNsfw)
{
var pld = new RestApplicationCommandEditPayload
{
Name = name,
Description = description,
Options = options,
DefaultMemberPermission = defaultMemberPermission,
DmPermission = dmPermission,
NameLocalizations = nameLocalization.Map(l => l.GetKeyValuePairs()).ValueOrDefault(),
DescriptionLocalizations = descriptionLocalization.Map(l => l.GetKeyValuePairs()).ValueOrDefault(),
Nsfw = isNsfw
};
var route = $"{Endpoints.APPLICATIONS}/:application_id{Endpoints.COMMANDS}/:command_id";
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<DiscordApplicationCommand>(res.Response);
ret.Discord = this.Discord;
return ret;
}
/// <summary>
/// Deletes a global application command.
/// </summary>
/// <param name="applicationId">The application_id.</param>
/// <param name="commandId">The command_id.</param>
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 = 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);
}
/// <summary>
/// Gets the guild application commands.
/// </summary>
/// <param name="applicationId">The application id.</param>
/// <param name="guildId">The guild id.</param>
/// <param name="withLocalizations">Whether to get the full localization dict.</param>
internal async Task<IReadOnlyList<DiscordApplicationCommand>> GetGuildApplicationCommandsAsync(ulong applicationId, ulong guildId, bool withLocalizations = false)
{
var route = $"{Endpoints.APPLICATIONS}/:application_id{Endpoints.GUILDS}/:guild_id{Endpoints.COMMANDS}";
var bucket = this.Rest.GetBucket(RestRequestMethod.GET, route, new {application_id = applicationId, guild_id = guildId }, out var path);
var querydict = new Dictionary<string, string>
{
["with_localizations"] = withLocalizations.ToString().ToLower()
};
var url = Utilities.GetApiUriFor(path, BuildQueryString(querydict), this.Discord.Configuration);
var res = await this.DoRequestAsync(this.Discord, bucket, url, RestRequestMethod.GET, route);
var ret = JsonConvert.DeserializeObject<IEnumerable<DiscordApplicationCommand>>(res.Response);
foreach (var app in ret)
app.Discord = this.Discord;
return ret.ToList();
}
/// <summary>
/// Bulk overwrites the guild application commands.
/// </summary>
/// <param name="applicationId">The application id.</param>
/// <param name="guildId">The guild id.</param>
/// <param name="commands">The commands.</param>
internal async Task<IReadOnlyList<DiscordApplicationCommand>> BulkOverwriteGuildApplicationCommandsAsync(ulong applicationId, ulong guildId, IEnumerable<DiscordApplicationCommand> commands)
{
var pld = new List<RestApplicationCommandCreatePayload>();
foreach (var command in commands)
{
pld.Add(new RestApplicationCommandCreatePayload
{
Type = command.Type,
Name = command.Name,
Description = command.Type == ApplicationCommandType.ChatInput ? command.Description : null,
Options = command.Options,
NameLocalizations = command.NameLocalizations?.GetKeyValuePairs(),
DescriptionLocalizations = command.DescriptionLocalizations?.GetKeyValuePairs(),
DefaultMemberPermission = command.DefaultMemberPermissions,
DmPermission = command.DmPermission,
Nsfw = command.IsNsfw
});
}
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 = 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<IEnumerable<DiscordApplicationCommand>>(res.Response);
foreach (var app in ret)
app.Discord = this.Discord;
return ret.ToList();
}
/// <summary>
/// Creates a guild application command.
/// </summary>
/// <param name="applicationId">The application id.</param>
/// <param name="guildId">The guild id.</param>
/// <param name="command">The command.</param>
internal async Task<DiscordApplicationCommand> CreateGuildApplicationCommandAsync(ulong applicationId, ulong guildId, DiscordApplicationCommand command)
{
var pld = new RestApplicationCommandCreatePayload
{
Type = command.Type,
Name = command.Name,
Description = command.Type == ApplicationCommandType.ChatInput ? command.Description : null,
Options = command.Options,
NameLocalizations = command.NameLocalizations.GetKeyValuePairs(),
DescriptionLocalizations = command.DescriptionLocalizations.GetKeyValuePairs(),
DefaultMemberPermission = command.DefaultMemberPermissions,
DmPermission = command.DmPermission,
Nsfw = command.IsNsfw
};
var route = $"{Endpoints.APPLICATIONS}/:application_id{Endpoints.GUILDS}/:guild_id{Endpoints.COMMANDS}";
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<DiscordApplicationCommand>(res.Response);
ret.Discord = this.Discord;
return ret;
}
/// <summary>
/// Gets a guild application command.
/// </summary>
/// <param name="applicationId">The application id.</param>
/// <param name="guildId">The guild id.</param>
/// <param name="commandId">The command id.</param>
internal async Task<DiscordApplicationCommand> 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 = 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<DiscordApplicationCommand>(res.Response);
ret.Discord = this.Discord;
return ret;
}
/// <summary>
/// Edits a guild application command.
/// </summary>
/// <param name="applicationId">The application id.</param>
/// <param name="guildId">The guild id.</param>
/// <param name="commandId">The command id.</param>
/// <param name="name">The name.</param>
/// <param name="description">The description.</param>
/// <param name="options">The options.</param>
/// <param name="nameLocalization">The localizations of the name.</param>
/// <param name="descriptionLocalization">The localizations of the description.</param>
/// <param name="defaultMemberPermission">The default member permissions.</param>
/// <param name="dmPermission">The dm permission.</param>
/// <param name="isNsfw">Whether this command is marked as NSFW.</param>
internal async Task<DiscordApplicationCommand> EditGuildApplicationCommandAsync(ulong applicationId, ulong guildId, ulong commandId,
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
Optional<string> name, Optional<string> description, Optional<List<DiscordApplicationCommandOption>?> options,
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
Optional<DiscordApplicationCommandLocalization> nameLocalization, Optional<DiscordApplicationCommandLocalization> descriptionLocalization,
Optional<Permissions?> defaultMemberPermission, Optional<bool> dmPermission, Optional<bool> isNsfw)
{
var pld = new RestApplicationCommandEditPayload
{
Name = name,
Description = description,
Options = options,
DefaultMemberPermission = defaultMemberPermission,
DmPermission = dmPermission,
NameLocalizations = nameLocalization.Map(l => l.GetKeyValuePairs()).ValueOrDefault(),
DescriptionLocalizations = descriptionLocalization.Map(l => l.GetKeyValuePairs()).ValueOrDefault(),
Nsfw = isNsfw
};
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 = 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<DiscordApplicationCommand>(res.Response);
ret.Discord = this.Discord;
return ret;
}
/// <summary>
/// Deletes a guild application command.
/// </summary>
/// <param name="applicationId">The application id.</param>
/// <param name="guildId">The guild id.</param>
/// <param name="commandId">The command id.</param>
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 = 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);
}
#region Permissions 2.1
/// <summary>
/// Gets the guild application command permissions.
/// </summary>
/// <param name="applicationId">The target application id.</param>
/// <param name="guildId">The target guild id.</param>
internal async Task<IReadOnlyList<DiscordGuildApplicationCommandPermission>> 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 = 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<IEnumerable<DiscordGuildApplicationCommandPermission>>(res.Response);
foreach (var app in ret)
app.Discord = this.Discord;
return ret.ToList();
}
/// <summary>
/// Gets a guild application command permission.
/// </summary>
/// <param name="applicationId">The target application id.</param>
/// <param name="guildId">The target guild id.</param>
/// <param name="commandId">The target command id.</param>
internal async Task<DiscordGuildApplicationCommandPermission> GetGuildApplicationCommandPermissionAsync(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 = 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<DiscordGuildApplicationCommandPermission>(res.Response);
ret.Discord = this.Discord;
return ret;
}
#endregion
/// <summary>
/// Creates the interaction response.
/// </summary>
/// <param name="interactionId">The interaction id.</param>
/// <param name="interactionToken">The interaction token.</param>
/// <param name="type">The type.</param>
/// <param name="builder">The builder.</param>
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,
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 fileId = 0;
List<DiscordAttachment> attachments = new();
foreach (var file in builder.Files)
{
DiscordAttachment att = new()
{
Id = fileId,
Discord = this.Discord,
Description = file.Description,
FileName = file.FileName,
FileSize = null
};
attachments.Add(att);
fileId++;
}
pld.Attachments = attachments;
pld.Data.Attachments = attachments;
}
}
else
{
pld = new RestInteractionResponsePayload
{
Type = type,
Data = new DiscordInteractionApplicationCommandCallbackData
{
Content = null,
Embeds = null,
IsTts = null,
Mentions = null,
Flags = null,
Components = null,
Choices = builder.Choices,
Attachments = null
},
Attachments = null
};
}
var values = new Dictionary<string, string>();
if (builder != null)
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 = 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));
}
}
/// <summary>
/// Creates the interaction response.
/// </summary>
/// <param name="interactionId">The interaction id.</param>
/// <param name="interactionToken">The interaction token.</param>
/// <param name="type">The type.</param>
/// <param name="builder">The builder.</param>
internal async Task CreateInteractionModalResponseAsync(ulong interactionId, string interactionToken, InteractionResponseType type, DiscordInteractionModalBuilder builder)
{
if (builder.ModalComponents.Any(mc => mc.Components.Any(c => c.Type != 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<string, string>();
var route = $"{Endpoints.INTERACTIONS}/:interaction_id/:interaction_token{Endpoints.CALLBACK}";
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));
}
/// <summary>
/// Gets the original interaction response.
/// </summary>
/// <param name="applicationId">The application id.</param>
/// <param name="interactionToken">The interaction token.</param>
internal Task<DiscordMessage> GetOriginalInteractionResponseAsync(ulong applicationId, string interactionToken) =>
this.GetWebhookMessageAsync(applicationId, interactionToken, Endpoints.ORIGINAL, null);
/// <summary>
/// Edits the original interaction response.
/// </summary>
/// <param name="applicationId">The application id.</param>
/// <param name="interactionToken">The interaction token.</param>
/// <param name="builder">The builder.</param>
internal Task<DiscordMessage> EditOriginalInteractionResponseAsync(ulong applicationId, string interactionToken, DiscordWebhookBuilder builder) =>
this.EditWebhookMessageAsync(applicationId, interactionToken, Endpoints.ORIGINAL, builder, null);
/// <summary>
/// Deletes the original interaction response.
/// </summary>
/// <param name="applicationId">The application id.</param>
/// <param name="interactionToken">The interaction token.</param>
internal Task DeleteOriginalInteractionResponseAsync(ulong applicationId, string interactionToken) =>
this.DeleteWebhookMessageAsync(applicationId, interactionToken, Endpoints.ORIGINAL, null);
/// <summary>
/// Creates the followup message.
/// </summary>
/// <param name="applicationId">The application id.</param>
/// <param name="interactionToken">The interaction token.</param>
/// <param name="builder">The builder.</param>
internal async Task<DiscordMessage> 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<string, string>();
var pld = new RestFollowupMessageCreatePayload
{
Content = builder.Content,
IsTts = builder.IsTts,
Embeds = builder.Embeds,
Flags = builder.Flags,
Components = builder.Components
};
if (builder.Files != null && builder.Files.Count > 0)
{
ulong fileId = 0;
List<DiscordAttachment> attachments = new();
foreach (var file in builder.Files)
{
DiscordAttachment att = new()
{
Id = fileId,
Discord = this.Discord,
Description = file.Description,
FileName = file.FileName,
FileSize = null
};
attachments.Add(att);
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)
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 = 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<DiscordMessage>(res.Response);
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;
}
/// <summary>
/// Gets the followup message.
/// </summary>
/// <param name="applicationId">The application id.</param>
/// <param name="interactionToken">The interaction token.</param>
/// <param name="messageId">The message id.</param>
internal Task<DiscordMessage> GetFollowupMessageAsync(ulong applicationId, string interactionToken, ulong messageId) =>
this.GetWebhookMessageAsync(applicationId, interactionToken, messageId);
/// <summary>
/// Edits the followup message.
/// </summary>
/// <param name="applicationId">The application id.</param>
/// <param name="interactionToken">The interaction token.</param>
/// <param name="messageId">The message id.</param>
/// <param name="builder">The builder.</param>
internal Task<DiscordMessage> EditFollowupMessageAsync(ulong applicationId, string interactionToken, ulong messageId, DiscordWebhookBuilder builder) =>
this.EditWebhookMessageAsync(applicationId, interactionToken, messageId.ToString(), builder, null);
/// <summary>
/// Deletes the followup message.
/// </summary>
/// <param name="applicationId">The application id.</param>
/// <param name="interactionToken">The interaction token.</param>
/// <param name="messageId">The message id.</param>
internal Task DeleteFollowupMessageAsync(ulong applicationId, string interactionToken, ulong messageId) =>
this.DeleteWebhookMessageAsync(applicationId, interactionToken, messageId);
#endregion
#region Misc
/// <summary>
/// Gets the current application info.
/// </summary>
internal Task<TransportApplication> GetCurrentApplicationInfoAsync()
=> this.GetApplicationInfoAsync("@me");
/// <summary>
/// Gets the application rpc info.
/// </summary>
/// <param name="applicationId">The application_id.</param>
internal Task<DiscordRpcApplication> GetApplicationInfoAsync(ulong applicationId)
=> this.GetApplicationRpcInfoAsync(applicationId.ToString(CultureInfo.InvariantCulture));
/// <summary>
/// Gets the application info.
/// </summary>
/// <param name="applicationId">The application_id.</param>
private async Task<TransportApplication> GetApplicationInfoAsync(string applicationId)
{
var route = $"{Endpoints.OAUTH2}{Endpoints.APPLICATIONS}/:application_id";
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<TransportApplication>(res.Response);
}
/// <summary>
/// Gets the application info.
/// </summary>
/// <param name="applicationId">The application_id.</param>
private async Task<DiscordRpcApplication> GetApplicationRpcInfoAsync(string applicationId)
{
var route = $"{Endpoints.APPLICATIONS}/:application_id{Endpoints.RPC}";
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<DiscordRpcApplication>(res.Response);
}
/// <summary>
/// Gets the application assets async.
/// </summary>
/// <param name="application">The application.</param>
internal async Task<IReadOnlyList<DiscordApplicationAsset>> 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<IEnumerable<DiscordApplicationAsset>>(res.Response);
foreach (var asset in assets)
{
asset.Discord = application.Discord;
asset.Application = application;
}
return new ReadOnlyCollection<DiscordApplicationAsset>(new List<DiscordApplicationAsset>(assets));
}
/// <summary>
/// Gets the gateway info async.
/// </summary>
internal async Task<GatewayInfo> 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<GatewayInfo>();
info.SessionBucket.ResetAfter = DateTimeOffset.UtcNow + TimeSpan.FromMilliseconds(info.SessionBucket.ResetAfterInternal);
return info;
}
#endregion
}
diff --git a/DisCatSharp/Net/Rest/Endpoints.cs b/DisCatSharp/Net/Rest/Endpoints.cs
index 6ed04d974..37d9c1aa1 100644
--- a/DisCatSharp/Net/Rest/Endpoints.cs
+++ b/DisCatSharp/Net/Rest/Endpoints.cs
@@ -1,506 +1,506 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net;
/// <summary>
/// The discord endpoints.
/// </summary>
public static class Endpoints
{
/// <summary>
/// The base discord api uri.
/// </summary>
public const string BASE_URI = "https://discord.com/api/v";
/// <summary>
/// The base discord canary api uri.
/// </summary>
public const string CANARY_URI = "https://canary.discord.com/api/v";
/// <summary>
/// The base discord ptb api uri.
/// </summary>
public const string PTB_URI = "https://ptb.discord.com/api/v";
/// <summary>
/// The oauth2 endpoint.
/// </summary>
public const string OAUTH2 = "/oauth2";
/// <summary>
/// The oauth2 authorize endpoint.
/// </summary>
public const string AUTHORIZE = "/authorize";
/// <summary>
/// The applications endpoint.
/// </summary>
public const string APPLICATIONS = "/applications";
/// <summary>
/// The message reactions endpoint.
/// </summary>
public const string REACTIONS = "/reactions";
/// <summary>
/// The self (@me) endpoint.
/// </summary>
public const string ME = "/@me";
/// <summary>
/// The @original endpoint.
/// </summary>
public const string ORIGINAL = "@original";
/// <summary>
/// The permissions endpoint.
/// </summary>
public const string PERMISSIONS = "/permissions";
/// <summary>
/// The recipients endpoint.
/// </summary>
public const string RECIPIENTS = "/recipients";
/// <summary>
/// The bulk-delete endpoint.
/// </summary>
public const string BULK_DELETE = "/bulk-delete";
/// <summary>
/// The integrations endpoint.
/// </summary>
public const string INTEGRATIONS = "/integrations";
/// <summary>
/// The sync endpoint.
/// </summary>
public const string SYNC = "/sync";
/// <summary>
/// The prune endpoint.
/// Used for user removal.
/// </summary>
public const string PRUNE = "/prune";
/// <summary>
/// The regions endpoint.
/// </summary>
public const string REGIONS = "/regions";
/// <summary>
/// The connections endpoint.
/// </summary>
public const string CONNECTIONS = "/connections";
/// <summary>
/// The icons endpoint.
/// </summary>
public const string ICONS = "/icons";
/// <summary>
/// The gateway endpoint.
/// </summary>
public const string GATEWAY = "/gateway";
/// <summary>
/// The oauth2 auth endpoint.
/// </summary>
public const string AUTH = "/auth";
/// <summary>
/// The oauth2 login endpoint.
/// </summary>
public const string LOGIN = "/login";
/// <summary>
/// The channels endpoint.
/// </summary>
public const string CHANNELS = "/channels";
/// <summary>
/// The messages endpoint.
/// </summary>
public const string MESSAGES = "/messages";
/// <summary>
/// The pinned messages endpoint.
/// </summary>
public const string PINS = "/pins";
/// <summary>
/// The users endpoint.
/// </summary>
public const string USERS = "/users";
/// <summary>
/// The guilds endpoint.
/// </summary>
public const string GUILDS = "/guilds";
/// <summary>
/// The guild discovery splash endpoint.
/// </summary>
public const string GUILD_DISCOVERY_SPLASHES = "/discovery-splashes";
/// <summary>
/// The guild splash endpoint.
/// </summary>
public const string SPLASHES = "/splashes";
/// <summary>
/// The search endpoint.
/// </summary>
public const string SEARCH = "/search";
/// <summary>
/// The invites endpoint.
/// </summary>
public const string INVITES = "/invites";
/// <summary>
/// The roles endpoint.
/// </summary>
public const string ROLES = "/roles";
/// <summary>
/// The members endpoint.
/// </summary>
public const string MEMBERS = "/members";
/// <summary>
/// The typing endpoint.
/// Triggers a typing indicator inside a channel.
/// </summary>
public const string TYPING = "/typing";
/// <summary>
/// The avatars endpoint.
/// </summary>
public const string AVATARS = "/avatars";
/// <summary>
/// The avatar decorations endpoint.
/// </summary>
public const string AVATARS_DECORATIONS = "/avatars-decorations";
/// <summary>
/// The bans endpoint.
/// </summary>
public const string BANS = "/bans";
/// <summary>
/// The webhook endpoint.
/// </summary>
public const string WEBHOOKS = "/webhooks";
/// <summary>
/// The slack endpoint.
/// Used for <see cref="Entities.DiscordWebhook"/>.
/// </summary>
public const string SLACK = "/slack";
/// <summary>
/// The github endpoint.
/// Used for <see cref="Entities.DiscordWebhook"/>.
/// </summary>
public const string GITHUB = "/github";
/// <summary>
/// The guilds mfa endpoint.
/// </summary>
public const string MFA = "/mfa";
/// <summary>
/// The bot endpoint.
/// </summary>
public const string BOT = "/bot";
/// <summary>
/// The voice endpoint.
/// </summary>
public const string VOICE = "/voice";
/// <summary>
/// The audit logs endpoint.
/// </summary>
public const string AUDIT_LOGS = "/audit-logs";
/// <summary>
/// The acknowledge endpoint.
/// Indicates that a message is read.
/// </summary>
public const string ACK = "/ack";
/// <summary>
/// The nickname endpoint.
/// </summary>
public const string NICK = "/nick";
/// <summary>
/// The assets endpoint.
/// </summary>
public const string ASSETS = "/assets";
/// <summary>
/// The embed endpoint.
/// </summary>
public const string EMBED = "/embed";
/// <summary>
/// The emojis endpoint.
/// </summary>
public const string EMOJIS = "/emojis";
/// <summary>
/// The vanity url endpoint.
/// </summary>
public const string VANITY_URL = "/vanity-url";
/// <summary>
/// The guild preview endpoint.
/// </summary>
public const string PREVIEW = "/preview";
/// <summary>
/// The followers endpoint.
/// </summary>
public const string FOLLOWERS = "/followers";
/// <summary>
/// The crosspost endpoint.
/// </summary>
public const string CROSSPOST = "/crosspost";
/// <summary>
/// The guild widget endpoint.
/// </summary>
public const string WIDGET = "/widget";
/// <summary>
/// The guild widget json endpoint.
/// </summary>
public const string WIDGET_JSON = "/widget.json";
/// <summary>
/// The guild widget png endpoint.
/// </summary>
public const string WIDGET_PNG = "/widget.png";
/// <summary>
/// The templates endpoint.
/// </summary>
public const string TEMPLATES = "/templates";
/// <summary>
/// The member verification gate endpoint.
/// </summary>
public const string MEMBER_VERIFICATION = "/member-verification";
/// <summary>
/// The slash commands endpoint.
/// </summary>
public const string COMMANDS = "/commands";
/// <summary>
/// The interactions endpoint.
/// </summary>
public const string INTERACTIONS = "/interactions";
/// <summary>
/// The interaction/command callback endpoint.
/// </summary>
public const string CALLBACK = "/callback";
/// <summary>
/// The welcome screen endpoint.
/// </summary>
public const string WELCOME_SCREEN = "/welcome-screen";
/// <summary>
/// The voice states endpoint.
/// </summary>
public const string VOICE_STATES = "/voice-states";
/// <summary>
/// The stage instances endpoint.
/// </summary>
public const string STAGE_INSTANCES = "/stage-instances";
/// <summary>
/// The threads endpoint.
/// </summary>
public const string THREADS = "/threads";
/// <summary>
/// The public threads endpoint.
/// </summary>
public const string THREAD_PUBLIC = "/public";
/// <summary>
/// The private threads endpoint.
/// </summary>
public const string THREAD_PRIVATE = "/private";
/// <summary>
/// The active threads endpoint.
/// </summary>
public const string THREAD_ACTIVE = "/active";
/// <summary>
/// The archived threads endpoint.
/// </summary>
public const string THREAD_ARCHIVED = "/archived";
/// <summary>
/// The thread members endpoint.
/// </summary>
public const string THREAD_MEMBERS = "/thread-members";
/// <summary>
/// The tags endpoint.
/// </summary>
public const string TAGS = "/tags";
/// <summary>
/// The guild scheduled events endpoint.
/// </summary>
public const string SCHEDULED_EVENTS = "/scheduled-events";
/// <summary>
/// The guild scheduled events cover image endpoint.
/// </summary>
public const string GUILD_EVENTS = "guild-events";
/// <summary>
/// The stickers endpoint.
/// </summary>
public const string STICKERS = "/stickers";
/// <summary>
/// The sticker packs endpoint.
/// Global nitro sticker packs.
/// </summary>
public const string STICKERPACKS = "/sticker-packs";
/// <summary>
/// The store endpoint.
/// </summary>
public const string STORE = "/store";
/// <summary>
/// The app assets endpoint.
/// </summary>
public const string APP_ASSETS = "/app-assets";
/// <summary>
/// The app icons endpoint.
/// </summary>
public const string APP_ICONS = "/app-icons";
/// <summary>
/// The team icons endpoint.
/// </summary>
public const string TEAM_ICONS = "/team-icons";
/// <summary>
/// The channel icons endpoint.
/// </summary>
public const string CHANNEL_ICONS = "/channel-icons";
/// <summary>
/// The user banners endpoint.
/// </summary>
public const string BANNERS = "/banners";
/// <summary>
/// The sticker endpoint.
/// This endpoint is the static nitro sticker application.
/// </summary>
public const string STICKER_APPLICATION = "/710982414301790216";
/// <summary>
/// The role subscription endpoint.
/// </summary>
public const string ROLE_SUBSCRIPTIONS = "/role-subscriptions";
/// <summary>
/// The group listings endpoint.
/// </summary>
public const string GROUP_LISTINGS = "/group-listings";
/// <summary>
/// The subscription listings endpoint.
/// </summary>
public const string SUBSCRIPTION_LISTINGS = "/subscription-listings";
/// <summary>
/// The directory entries endpoint.
/// </summary>
public const string DIRECTORY_ENTRIES = "/directory-entries";
/// <summary>
/// The counts endpoint.
/// </summary>
public const string COUNTS = "/counts";
/// <summary>
/// The list endpoint.
/// </summary>
public const string LIST = "/list";
/// <summary>
/// The role icons endpoint.
/// </summary>
public const string ROLE_ICONS = "/role-icons";
/// <summary>
/// The activities endpoint.
/// </summary>
public const string ACTIVITIES = "/activities";
/// <summary>
/// The config endpoint.
/// </summary>
public const string CONFIG = "/config";
/// <summary>
/// The ephemeral attachments endpoint.
/// </summary>
public const string EPHEMERAL_ATTACHMENTS = "/ephemeral-attachments";
/// <summary>
/// The rpc endpoint.
/// </summary>
public const string RPC = "/rpc";
/// <summary>
/// The role connections endpoint.
/// </summary>
public const string ROLE_CONNECTIONS = "/role-connections";
/// <summary>
/// The metadata endpoint.
/// </summary>
public const string METADATA = "/metadata";
}
diff --git a/DisCatSharp/Net/Rest/IpEndpoint.cs b/DisCatSharp/Net/Rest/IpEndpoint.cs
index 558f53fa9..d2e6c93e4 100644
--- a/DisCatSharp/Net/Rest/IpEndpoint.cs
+++ b/DisCatSharp/Net/Rest/IpEndpoint.cs
@@ -1,52 +1,52 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net;
namespace DisCatSharp.Net;
/// <summary>
/// Represents a network connection IP endpoint.
/// </summary>
public struct IpEndpoint
{
/// <summary>
/// Gets or sets the hostname associated with this endpoint.
/// </summary>
public IPAddress Address { get; set; }
/// <summary>
/// Gets or sets the port associated with this endpoint.
/// </summary>
public int Port { get; set; }
/// <summary>
/// Creates a new IP endpoint structure.
/// </summary>
/// <param name="address">IP address to connect to.</param>
/// <param name="port">Port to use for connection.</param>
public IpEndpoint(IPAddress address, int port)
{
this.Address = address;
this.Port = port;
}
}
diff --git a/DisCatSharp/Net/Rest/MultipartWebRequest.cs b/DisCatSharp/Net/Rest/MultipartWebRequest.cs
index 4a6aabbef..407b6ae38 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-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a multipart HTTP request.
/// </summary>
internal sealed class MultipartWebRequest : BaseRestRequest
{
/// <summary>
/// Gets the dictionary of values attached to this request.
/// </summary>
public IReadOnlyDictionary<string, string> Values { get; }
/// <summary>
/// Gets the dictionary of files attached to this request.
/// </summary>
public IReadOnlyDictionary<string, Stream> Files { get; }
/// <summary>
/// Overwrites the file id start.
/// </summary>
public int? OverwriteFileIdStart { get; }
/// <summary>
/// Initializes a new instance of the <see cref="MultipartWebRequest"/> class.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="bucket">The bucket.</param>
/// <param name="url">The url.</param>
/// <param name="method">The method.</param>
/// <param name="route">The route.</param>
/// <param name="headers">The headers.</param>
/// <param name="values">The values.</param>
/// <param name="files">The files.</param>
/// <param name="ratelimitWaitOverride">The ratelimit_wait_override.</param>
/// <param name="overwriteFileIdStart">The file id start.</param>
internal MultipartWebRequest(BaseDiscordClient client, RateLimitBucket bucket, Uri url, RestRequestMethod method, string route, IReadOnlyDictionary<string, string> headers = null, IReadOnlyDictionary<string, string> values = null,
IReadOnlyCollection<DiscordMessageFile> files = null, double? ratelimitWaitOverride = null, int? overwriteFileIdStart = null)
: base(client, bucket, url, method, route, headers, ratelimitWaitOverride)
{
this.Values = values;
this.OverwriteFileIdStart = overwriteFileIdStart;
this.Files = files.ToDictionary(x => x.FileName, x => x.Stream);
}
}
/// <summary>
/// Represents a multipart HTTP request for stickers.
/// </summary>
internal sealed class MultipartStickerWebRequest : BaseRestRequest
{
/// <summary>
/// Gets the file.
/// </summary>
public DiscordMessageFile File { get; }
/// <summary>
/// Gets the name.
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the description.
/// </summary>
public string Description { get; }
/// <summary>
/// Gets the tags.
/// </summary>
public string Tags { get; }
/// <summary>
/// Initializes a new instance of the <see cref="MultipartStickerWebRequest"/> class.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="bucket">The bucket.</param>
/// <param name="url">The url.</param>
/// <param name="method">The method.</param>
/// <param name="route">The route.</param>
/// <param name="headers">The headers.</param>
/// <param name="file">The file.</param>
/// <param name="name">The sticker name.</param>
/// <param name="tags">The sticker tag.</param>
/// <param name="description">The sticker description.</param>
/// <param name="ratelimitWaitOverride">The ratelimit_wait_override.</param>
internal MultipartStickerWebRequest(BaseDiscordClient client, RateLimitBucket bucket, Uri url, RestRequestMethod method, string route, IReadOnlyDictionary<string, string> headers = null,
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 cdd0d844e..b88203a52 100644
--- a/DisCatSharp/Net/Rest/RateLimitBucket.cs
+++ b/DisCatSharp/Net/Rest/RateLimitBucket.cs
@@ -1,290 +1,290 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a rate limit bucket.
/// </summary>
internal class RateLimitBucket : IEquatable<RateLimitBucket>
{
/// <summary>
/// Gets the Id of the guild bucket.
/// </summary>
public string GuildId { get; internal set; }
/// <summary>
/// Gets the Id of the channel bucket.
/// </summary>
public string ChannelId { get; internal set; }
/// <summary>
/// Gets the ID of the webhook bucket.
/// </summary>
public string WebhookId { get; internal set; }
/// <summary>
/// Gets the Id of the ratelimit bucket.
/// </summary>
public volatile string BucketId;
/// <summary>
/// Gets or sets the ratelimit hash of this bucket.
/// </summary>
public string Hash
{
get => Volatile.Read(ref this.HashInternal);
internal set
{
this.IsUnlimited = value.Contains(s_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.HashInternal, value);
}
}
internal string HashInternal;
/// <summary>
/// Gets the past route hashes associated with this bucket.
/// </summary>
public ConcurrentBag<string> RouteHashes { get; }
/// <summary>
/// Gets when this bucket was last called in a request.
/// </summary>
public DateTimeOffset LastAttemptAt { get; internal set; }
/// <summary>
/// Gets the number of uses left before pre-emptive rate limit is triggered.
/// </summary>
public int Remaining
=> this.RemainingInternal;
/// <summary>
/// Gets the maximum number of uses within a single bucket.
/// </summary>
public int Maximum { get; set; }
/// <summary>
/// Gets the timestamp at which the rate limit resets.
/// </summary>
public DateTimeOffset Reset { get; internal set; }
/// <summary>
/// Gets the time interval to wait before the rate limit resets.
/// </summary>
public TimeSpan? ResetAfter { get; internal set; }
/// <summary>
/// Gets a value indicating whether the ratelimit global.
/// </summary>
public bool IsGlobal { get; internal set; } = false;
/// <summary>
/// Gets the ratelimit scope.
/// </summary>
public string Scope { get; internal set; } = "user";
/// <summary>
/// Gets the time interval to wait before the rate limit resets as offset
/// </summary>
internal DateTimeOffset ResetAfterOffset { get; set; }
internal volatile int RemainingInternal;
/// <summary>
/// Gets whether this bucket has it's ratelimit determined.
/// <para>This will be <see langword="false"/> if the ratelimit is determined.</para>
/// </summary>
internal volatile bool IsUnlimited;
/// <summary>
/// If the initial request for this bucket that is determining the rate limits is currently executing
/// This is a int because booleans can't be accessed atomically
/// 0 => False, all other values => True
/// </summary>
internal volatile int LimitTesting;
/// <summary>
/// Task to wait for the rate limit test to finish
/// </summary>
internal volatile Task LimitTestFinished;
/// <summary>
/// If the rate limits have been determined
/// </summary>
internal volatile bool LimitValid;
/// <summary>
/// Rate limit reset in ticks, UTC on the next response after the rate limit has been reset
/// </summary>
internal long NextReset;
/// <summary>
/// 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
/// </summary>
internal volatile int LimitResetting;
private static readonly string s_unlimitedHash = "unlimited";
/// <summary>
/// Initializes a new instance of the <see cref="RateLimitBucket"/> class.
/// </summary>
/// <param name="hash">The hash.</param>
/// <param name="guildId">The guild_id.</param>
/// <param name="channelId">The channel_id.</param>
/// <param name="webhookId">The webhook_id.</param>
internal RateLimitBucket(string hash, string guildId, string channelId, string webhookId)
{
this.Hash = hash;
this.ChannelId = channelId;
this.GuildId = guildId;
this.WebhookId = webhookId;
this.BucketId = GenerateBucketId(hash, guildId, channelId, webhookId);
this.RouteHashes = new ConcurrentBag<string>();
}
/// <summary>
/// Generates an ID for this request bucket.
/// </summary>
/// <param name="hash">Hash for this bucket.</param>
/// <param name="guildId">Guild Id for this bucket.</param>
/// <param name="channelId">Channel Id for this bucket.</param>
/// <param name="webhookId">Webhook Id for this bucket.</param>
/// <returns>Bucket Id.</returns>
public static string GenerateBucketId(string hash, string guildId, string channelId, string webhookId)
=> $"{hash}:{guildId}:{channelId}:{webhookId}";
/// <summary>
/// Generates the hash key.
/// </summary>
/// <param name="method">The method.</param>
/// <param name="route">The route.</param>
/// <returns>A string.</returns>
public static string GenerateHashKey(RestRequestMethod method, string route)
=> $"{method}:{route}";
/// <summary>
/// Generates the unlimited hash.
/// </summary>
/// <param name="method">The method.</param>
/// <param name="route">The route.</param>
/// <returns>A string.</returns>
public static string GenerateUnlimitedHash(RestRequestMethod method, string route)
=> $"{GenerateHashKey(method, route)}:{s_unlimitedHash}";
/// <summary>
/// Returns a string representation of this bucket.
/// </summary>
/// <returns>String representation of this bucket.</returns>
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)}";
}
/// <summary>
/// Checks whether this <see cref="RateLimitBucket"/> is equal to another object.
/// </summary>
/// <param name="obj">Object to compare to.</param>
/// <returns>Whether the object is equal to this <see cref="RateLimitBucket"/>.</returns>
public override bool Equals(object obj)
=> this.Equals(obj as RateLimitBucket);
/// <summary>
/// Checks whether this <see cref="RateLimitBucket"/> is equal to another <see cref="RateLimitBucket"/>.
/// </summary>
/// <param name="e"><see cref="RateLimitBucket"/> to compare to.</param>
/// <returns>Whether the <see cref="RateLimitBucket"/> is equal to this <see cref="RateLimitBucket"/>.</returns>
public bool Equals(RateLimitBucket e) => e is not null && (ReferenceEquals(this, e) || this.BucketId == e.BucketId);
/// <summary>
/// Gets the hash code for this <see cref="RateLimitBucket"/>.
/// </summary>
/// <returns>The hash code for this <see cref="RateLimitBucket"/>.</returns>
public override int GetHashCode()
=> this.BucketId.GetHashCode();
/// <summary>
/// Sets remaining number of requests to the maximum when the ratelimit is reset
/// </summary>
/// <param name="now">The datetime offset.</param>
internal async Task TryResetLimitAsync(DateTimeOffset now)
{
if (this.ResetAfter.HasValue)
this.ResetAfter = this.ResetAfterOffset - now;
if (this.NextReset == 0)
return;
if (this.NextReset > now.UtcTicks)
return;
while (Interlocked.CompareExchange(ref this.LimitResetting, 1, 0) != 0)
#pragma warning restore 420
await Task.Yield();
if (this.NextReset != 0)
{
this.RemainingInternal = this.Maximum;
this.NextReset = 0;
}
this.LimitResetting = 0;
}
/// <summary>
/// Sets the initial values.
/// </summary>
/// <param name="max">The max.</param>
/// <param name="usesLeft">The uses left.</param>
/// <param name="newReset">The new reset.</param>
internal void SetInitialValues(int max, int usesLeft, DateTimeOffset newReset)
{
this.Maximum = max;
this.RemainingInternal = usesLeft;
this.NextReset = newReset.UtcTicks;
this.LimitValid = true;
this.LimitTestFinished = null;
this.LimitTesting = 0;
}
}
diff --git a/DisCatSharp/Net/Rest/RestClient.cs b/DisCatSharp/Net/Rest/RestClient.cs
index 349b5d944..d515d55bc 100644
--- a/DisCatSharp/Net/Rest/RestClient.cs
+++ b/DisCatSharp/Net/Rest/RestClient.cs
@@ -1,870 +1,870 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents a client used to make REST requests.
/// </summary>
internal sealed class RestClient : IDisposable
{
/// <summary>
/// Gets the route argument regex.
/// </summary>
private static Regex s_routeArgumentRegex { get; } = new(@":([a-z_]+)");
/// <summary>
/// Gets the http client.
/// </summary>
internal HttpClient HttpClient { get; }
/// <summary>
/// Gets the discord client.
/// </summary>
private readonly BaseDiscordClient _discord;
/// <summary>
/// Gets a value indicating whether debug is enabled.
/// </summary>
internal bool Debug { get; set; }
/// <summary>
/// Gets the logger.
/// </summary>
private readonly ILogger _logger;
/// <summary>
/// Gets the routes to hashes.
/// </summary>
private readonly ConcurrentDictionary<string, string> _routesToHashes;
/// <summary>
/// Gets the hashes to buckets.
/// </summary>
private readonly ConcurrentDictionary<string, RateLimitBucket> _hashesToBuckets;
/// <summary>
/// Gets the request queue.
/// </summary>
private readonly ConcurrentDictionary<string, int> _requestQueue;
/// <summary>
/// Gets the global rate limit event.
/// </summary>
private readonly AsyncManualResetEvent _globalRateLimitEvent;
/// <summary>
/// Gets a value indicating whether use reset after.
/// </summary>
private readonly bool _useResetAfter;
private CancellationTokenSource _bucketCleanerTokenSource;
private readonly TimeSpan _bucketCleanupDelay = TimeSpan.FromSeconds(60);
private volatile bool _cleanerRunning;
private Task _cleanerTask;
private volatile bool _disposed;
/// <summary>
/// Initializes a new instance of the <see cref="RestClient"/> class.
/// </summary>
/// <param name="client">The client.</param>
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);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="RestClient"/> class.
/// This is for meta-clients, such as the webhook client.
/// </summary>
/// <param name="proxy">The proxy.</param>
/// <param name="timeout">The timeout.</param>
/// <param name="useRelativeRatelimit">Whether to use relative ratelimit.</param>
/// <param name="logger">The logger.</param>
internal RestClient(IWebProxy proxy, TimeSpan timeout, bool useRelativeRatelimit,
ILogger logger)
{
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.HttpClient.DefaultRequestHeaders.TryAddWithoutValidation("X-Discord-Locale", this._discord?.Configuration?.Locale ?? "en-US");
this._routesToHashes = new ConcurrentDictionary<string, string>();
this._hashesToBuckets = new ConcurrentDictionary<string, RateLimitBucket>();
this._requestQueue = new ConcurrentDictionary<string, int>();
this._globalRateLimitEvent = new AsyncManualResetEvent(true);
this._useResetAfter = useRelativeRatelimit;
}
/// <summary>
/// Gets a ratelimit bucket.
/// </summary>
/// <param name="method">The method.</param>
/// <param name="route">The route.</param>
/// <param name="routeParams">The route parameters.</param>
/// <param name="url">The url.</param>
/// <returns>A ratelimit bucket.</returns>
public RateLimitBucket GetBucket(RestRequestMethod method, string route, object routeParams, out string url)
{
var rparamsProps = routeParams.GetType()
.GetTypeInfo()
.DeclaredProperties;
var rparams = new Dictionary<string, string>();
foreach (var xp in rparamsProps)
{
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 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, 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, 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 = s_routeArgumentRegex.Replace(route, xm => rparams[xm.Groups[1].Value]);
return bucket;
}
/// <summary>
/// Executes the request.
/// </summary>
/// <param name="request">The request to be executed.</param>
public Task ExecuteRequestAsync(BaseRestRequest request)
=> request == null ? throw new ArgumentNullException(nameof(request)) : this.ExecuteRequestAsync(request, null, null);
/// <summary>
/// Executes the request.
/// This is to allow proper rescheduling of the first request from a bucket.
/// </summary>
/// <param name="request">The request to be executed.</param>
/// <param name="bucket">The bucket.</param>
/// <param name="ratelimitTcs">The ratelimit task completion source.</param>
private async Task ExecuteRequestAsync(BaseRestRequest request, RateLimitBucket bucket, TaskCompletionSource<bool> ratelimitTcs)
{
if (this._disposed)
return;
HttpResponseMessage res = default;
try
{
await this._globalRateLimitEvent.WaitAsync().ConfigureAwait(false);
bucket ??= request.RateLimitBucket;
ratelimitTcs ??= await this.WaitForInitialRateLimit(bucket).ConfigureAwait(false);
if (ratelimitTcs == null) // check 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.RemainingInternal) < 0)
{
this._logger.LogWarning(LoggerEvents.RatelimitDiag, "Request for {bucket} is blocked. Url: {url}", bucket.ToString(), request.Url.AbsoluteUri);
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.RemainingInternal = 1;
}
if (delay < TimeSpan.Zero)
delay = TimeSpan.FromMilliseconds(100);
this._logger.LogWarning(LoggerEvents.RatelimitPreemptive, "Preemptive 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 {bucket} is allowed. Url: {url}", bucket.ToString(), request.Url.AbsoluteUri);
}
else
this._logger.LogDebug(LoggerEvents.RatelimitDiag, "Initial request for {bucket} is allowed. Url: {url}", bucket.ToString(), request.Url.AbsoluteUri);
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.AbsoluteUri);
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 for {uri}", request.Url.AbsoluteUri);
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
{
if (this._discord is DiscordClient)
{
await (this._discord as DiscordClient)._rateLimitHit.InvokeAsync(this._discord as DiscordClient, new EventArgs.RateLimitExceptionEventArgs(this._discord.ServiceProvider)
{
Exception = ex as RateLimitException,
ApiEndpoint = request.Url.AbsoluteUri
});
}
this._logger.LogError(LoggerEvents.RatelimitHit, "Ratelimit hit, requeuing request to {url}", request.Url.AbsoluteUri);
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.AbsoluteUri);
// 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)
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 _);
}
}
}
}
}
/// <summary>
/// Fails the initial rate limit test.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="ratelimitTcs">The ratelimit task completion source.</param>
/// <param name="resetToInitial">Whether to reset to initial values.</param>
private void FailInitialRateLimitTest(BaseRestRequest request, TaskCompletionSource<bool> ratelimitTcs, bool resetToInitial = false)
{
if (ratelimitTcs == null && !resetToInitial)
return;
var bucket = request.RateLimitBucket;
bucket.LimitValid = false;
bucket.LimitTestFinished = null;
bucket.LimitTesting = 0;
//Reset to initial values.
if (resetToInitial)
{
this.UpdateHashCaches(request, bucket);
bucket.Maximum = 0;
bucket.RemainingInternal = 0;
return;
}
// no need to wait on all the potentially waiting tasks
_ = Task.Run(() => ratelimitTcs.TrySetResult(false));
}
/// <summary>
/// Waits for the initial rate limit.
/// </summary>
/// <param name="bucket">The bucket.</param>
private async Task<TaskCompletionSource<bool>> WaitForInitialRateLimit(RateLimitBucket bucket)
{
while (!bucket.LimitValid)
{
if (bucket.LimitTesting == 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 signal ExecuteRequestAsync to bypass rate limiting
if (bucket.LimitValid)
return null;
// allow exactly one request to go through without having rate limits available
var ratelimitsTcs = new TaskCompletionSource<bool>();
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)
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;
}
/// <summary>
/// Builds the request.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>A http request message.</returns>
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, "<multipart request>");
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, "<multipart request>");
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;
}
/// <summary>
/// Handles the HTTP 429 status.
/// </summary>
/// <param name="response">The response.</param>
/// <param name="waitTask">The wait task.</param>
/// <param name="global">If true, global.</param>
private void Handle429(RestResponse response, out Task waitTask, out bool global)
{
waitTask = null;
global = false;
if (response.Headers == null)
return;
var hs = response.Headers;
// handle the wait
if (hs.TryGetValue("Retry-After", out var retryAfterRaw))
{
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;
}
}
/// <summary>
/// Updates the bucket.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="response">The response.</param>
/// <param name="ratelimitTcs">The ratelimit task completion source.</param>
private void UpdateBucket(BaseRestRequest request, RestResponse response, TaskCompletionSource<bool> 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 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;
}
this.UpdateHashCaches(request, bucket, hash);
}
/// <summary>
/// Updates the hash caches.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="bucket">The bucket.</param>
/// <param name="newHash">The new hash.</param>
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)
{
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;
}
/// <summary>
/// Cleans the buckets.
/// </summary>
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)
{
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)
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 (!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.IsEmpty)
break;
}
if (!this._bucketCleanerTokenSource.IsCancellationRequested)
this._bucketCleanerTokenSource.Cancel();
this._cleanerRunning = false;
this._logger.LogDebug(LoggerEvents.RestCleaner, "Bucket cleaner task stopped.");
}
~RestClient()
{
this.Dispose();
}
/// <summary>
/// Disposes the rest client.
/// </summary>
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();
GC.SuppressFinalize(this);
}
}
diff --git a/DisCatSharp/Net/Rest/RestRequest.cs b/DisCatSharp/Net/Rest/RestRequest.cs
index d75963779..54d7a760f 100644
--- a/DisCatSharp/Net/Rest/RestRequest.cs
+++ b/DisCatSharp/Net/Rest/RestRequest.cs
@@ -1,54 +1,54 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net;
/// <summary>
/// Represents a non-multipart HTTP request.
/// </summary>
internal sealed class RestRequest : BaseRestRequest
{
/// <summary>
/// Gets the payload sent with this request.
/// </summary>
public string Payload { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RestRequest"/> class.
/// </summary>
/// <param name="client">The client.</param>
/// <param name="bucket">The bucket.</param>
/// <param name="url">The url.</param>
/// <param name="method">The method.</param>
/// <param name="route">The route.</param>
/// <param name="headers">The headers.</param>
/// <param name="payload">The payload.</param>
/// <param name="ratelimitWaitOverride">The ratelimit wait override.</param>
internal RestRequest(BaseDiscordClient client, RateLimitBucket bucket, Uri url, RestRequestMethod method, string route, IReadOnlyDictionary<string, string> headers = null, string payload = null, double? ratelimitWaitOverride = null)
: base(client, bucket, url, method, route, headers, ratelimitWaitOverride)
{
this.Payload = payload;
}
}
diff --git a/DisCatSharp/Net/Rest/RestRequestMethod.cs b/DisCatSharp/Net/Rest/RestRequestMethod.cs
index 4fd2cb679..8bef0ce1a 100644
--- a/DisCatSharp/Net/Rest/RestRequestMethod.cs
+++ b/DisCatSharp/Net/Rest/RestRequestMethod.cs
@@ -1,60 +1,60 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Defines the HTTP method to use for an HTTP request.
/// </summary>
public enum RestRequestMethod : int
{
/// <summary>
/// Defines that the request is a GET request.
/// </summary>
GET = 0,
/// <summary>
/// Defines that the request is a POST request.
/// </summary>
POST = 1,
/// <summary>
/// Defines that the request is a DELETE request.
/// </summary>
DELETE = 2,
/// <summary>
/// Defines that the request is a PATCH request.
/// </summary>
PATCH = 3,
/// <summary>
/// Defines that the request is a PUT request.
/// </summary>
PUT = 4,
/// <summary>
/// Defines that the request is a HEAD request.
/// </summary>
HEAD = 5
}
diff --git a/DisCatSharp/Net/Rest/RestResponse.cs b/DisCatSharp/Net/Rest/RestResponse.cs
index f2f621bd3..872e91492 100644
--- a/DisCatSharp/Net/Rest/RestResponse.cs
+++ b/DisCatSharp/Net/Rest/RestResponse.cs
@@ -1,51 +1,51 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
namespace DisCatSharp.Net;
/// <summary>
/// Represents a response sent by the remote HTTP party.
/// </summary>
public sealed class RestResponse
{
/// <summary>
/// Gets the response code sent by the remote party.
/// </summary>
public int ResponseCode { get; internal set; }
/// <summary>
/// Gets the headers sent by the remote party.
/// </summary>
public IReadOnlyDictionary<string, string> Headers { get; internal set; }
/// <summary>
/// Gets the contents of the response sent by the remote party.
/// </summary>
public string Response { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="RestResponse"/> class.
/// </summary>
internal RestResponse() { }
}
diff --git a/DisCatSharp/Net/Rest/SessionBucket.cs b/DisCatSharp/Net/Rest/SessionBucket.cs
index 3da82e727..9b007cfd8 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-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// Represents the bucket limits for identifying to Discord.
/// <para>This is only relevant for clients that are manually sharding.</para>
/// </summary>
public class SessionBucket
{
/// <summary>
/// Gets the total amount of sessions per token.
/// </summary>
[JsonProperty("total")]
public int Total { get; internal set; }
/// <summary>
/// Gets the remaining amount of sessions for this token.
/// </summary>
[JsonProperty("remaining")]
public int Remaining { get; internal set; }
/// <summary>
/// Gets the datetime when the <see cref="Remaining"/> will reset.
/// </summary>
[JsonIgnore]
public DateTimeOffset ResetAfter { get; internal set; }
/// <summary>
/// Gets the maximum amount of shards that can boot concurrently.
/// </summary>
[JsonProperty("max_concurrency")]
public int MaxConcurrency { get; internal set; }
/// <summary>
/// Gets the reset after value.
/// </summary>
[JsonProperty("reset_after")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "<Pending>")]
internal int ResetAfterInternal { get; set; }
/// <summary>
/// Returns a readable session bucket string.
/// </summary>
public override string ToString()
=> $"[{this.Remaining}/{this.Total}] {this.ResetAfter}. {this.MaxConcurrency}x concurrency";
}
diff --git a/DisCatSharp/Net/Serialization/DiscordComponentJsonConverter.cs b/DisCatSharp/Net/Serialization/DiscordComponentJsonConverter.cs
index 9a0783c67..45c8eb854 100644
--- a/DisCatSharp/Net/Serialization/DiscordComponentJsonConverter.cs
+++ b/DisCatSharp/Net/Serialization/DiscordComponentJsonConverter.cs
@@ -1,96 +1,96 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace DisCatSharp.Net.Serialization;
/// <summary>
/// Represents a discord component json converter.
/// </summary>
internal sealed class DiscordComponentJsonConverter : JsonConverter
{
/// <summary>
/// Whether the converter can write.
/// </summary>
public override bool CanWrite => false;
/// <summary>
/// Writes the json.
/// </summary>
/// <param name="writer">The writer.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The serializer.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
/// <summary>
/// Reads the json.
/// </summary>
/// <param name="reader">The reader.</param>
/// <param name="objectType">The object type.</param>
/// <param name="existingValue">The existing value.</param>
/// <param name="serializer">The serializer.</param>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var job = JObject.Load(reader);
var type = job["type"]?.ToObject<ComponentType>();
if (type == null)
throw new ArgumentException($"Value {reader} does not have a component type specifier");
DiscordComponent cmp;
cmp = type switch
{
ComponentType.ActionRow => new DiscordActionRowComponent(),
ComponentType.Button when (string)job["url"] is not null => new DiscordLinkButtonComponent(),
ComponentType.Button => new DiscordButtonComponent(),
ComponentType.StringSelect => new DiscordStringSelectComponent(),
ComponentType.InputText => new DiscordTextComponent(),
ComponentType.UserSelect => new DiscordUserSelectComponent(),
ComponentType.RoleSelect => new DiscordRoleSelectComponent(),
ComponentType.MentionableSelect => new DiscordMentionableSelectComponent(),
ComponentType.ChannelSelect => new DiscordChannelSelectComponent(),
_ => new DiscordComponent() { Type = type.Value }
};
// Populate the existing component with the values in the JObject. This avoids a recursive JsonConverter loop
using var jreader = job.CreateReader();
serializer.Populate(jreader, cmp);
return cmp;
}
/// <summary>
/// Whether the json can convert.
/// </summary>
/// <param name="objectType">The object type.</param>
public override bool CanConvert(Type objectType) => typeof(DiscordComponent).IsAssignableFrom(objectType);
}
diff --git a/DisCatSharp/Net/Serialization/DiscordJson.cs b/DisCatSharp/Net/Serialization/DiscordJson.cs
index 6abbc49bb..8300323c2 100644
--- a/DisCatSharp/Net/Serialization/DiscordJson.cs
+++ b/DisCatSharp/Net/Serialization/DiscordJson.cs
@@ -1,83 +1,83 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.IO;
using System.Text;
using DisCatSharp.Entities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace DisCatSharp.Net.Serialization;
/// <summary>
/// Represents discord json.
/// </summary>
public static class DiscordJson
{
private static readonly JsonSerializer s_serializer = JsonSerializer.CreateDefault(new JsonSerializerSettings
{
ContractResolver = new OptionalJsonContractResolver()
});
/// <summary>Serializes the specified object to a JSON string.</summary>
/// <param name="value">The object to serialize.</param>
/// <returns>A JSON string representation of the object.</returns>
public static string SerializeObject(object value) => SerializeObjectInternal(value, null, s_serializer);
/// <summary>Populates an object with the values from a JSON node.</summary>
/// <param name="value">The token to populate the object with.</param>
/// <param name="target">The object to populate.</param>
public static void PopulateObject(JToken value, object target)
{
using var reader = value.CreateReader();
s_serializer.Populate(reader, target);
}
/// <summary>
/// Converts this token into an object, passing any properties through extra <see cref="Newtonsoft.Json.JsonConverter"/>s if needed.
/// </summary>
/// <param name="token">The token to convert</param>
/// <typeparam name="T">Type to convert to</typeparam>
/// <returns>The converted token</returns>
public static T ToDiscordObject<T>(this JToken token) => token.ToObject<T>(s_serializer);
/// <summary>
/// Serializes the object.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="type">The type.</param>
/// <param name="jsonSerializer">The json serializer.</param>
private static string SerializeObjectInternal(object value, Type type, JsonSerializer jsonSerializer)
{
var stringWriter = new StringWriter(new StringBuilder(256), CultureInfo.InvariantCulture);
using (var jsonTextWriter = new JsonTextWriter(stringWriter))
{
jsonTextWriter.Formatting = jsonSerializer.Formatting;
jsonSerializer.Serialize(jsonTextWriter, value, type);
}
return stringWriter.ToString();
}
}
diff --git a/DisCatSharp/Net/Serialization/SnowflakeArrayAsDictionaryJsonConverter.cs b/DisCatSharp/Net/Serialization/SnowflakeArrayAsDictionaryJsonConverter.cs
index bd8c6821a..f11428977 100644
--- a/DisCatSharp/Net/Serialization/SnowflakeArrayAsDictionaryJsonConverter.cs
+++ b/DisCatSharp/Net/Serialization/SnowflakeArrayAsDictionaryJsonConverter.cs
@@ -1,110 +1,110 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using DisCatSharp.Entities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace DisCatSharp.Net.Serialization;
/// <summary>
/// Used for a <see cref="Dictionary{TKey,TValue}"/> or <see cref="ConcurrentDictionary{TKey,TValue}"/> mapping
/// <see cref="ulong"/> to any class extending <see cref="SnowflakeObject"/> (or, as a special case,
/// <see cref="DiscordVoiceState"/>). When serializing, discards the ulong
/// keys and writes only the values. When deserializing, pulls the keys from <see cref="SnowflakeObject.Id"/> (or,
/// in the case of <see cref="DiscordVoiceState"/>, <see cref="DiscordVoiceState.UserId"/>.
/// </summary>
internal class SnowflakeArrayAsDictionaryJsonConverter : JsonConverter
{
/// <summary>
/// Writes the json.
/// </summary>
/// <param name="writer">The writer.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The serializer.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null)
{
writer.WriteNull();
}
else
{
var type = value.GetType().GetTypeInfo();
JToken.FromObject(type.GetDeclaredProperty("Values").GetValue(value)).WriteTo(writer);
}
}
/// <summary>
/// Reads the json.
/// </summary>
/// <param name="reader">The reader.</param>
/// <param name="objectType">The object type.</param>
/// <param name="existingValue">The existing value.</param>
/// <param name="serializer">The serializer.</param>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var constructor = objectType.GetTypeInfo().DeclaredConstructors
.FirstOrDefault(e => !e.IsStatic && e.GetParameters().Length == 0);
var dict = constructor.Invoke(Array.Empty<object>());
// the default name of an indexer is "Item"
var properties = objectType.GetTypeInfo().GetDeclaredProperty("Item");
var entries = (IEnumerable) serializer.Deserialize(reader, objectType.GenericTypeArguments[1].MakeArrayType());
foreach (var entry in entries)
{
properties.SetValue(dict, entry, new object[]
{
(entry as SnowflakeObject)?.Id
?? (entry as DiscordVoiceState)?.UserId
?? throw new InvalidOperationException($"Type {entry?.GetType()} is not deserializable")
});
}
return dict;
}
/// <summary>
/// Whether the snowflake can be converted.
/// </summary>
/// <param name="objectType">The object type.</param>
public override bool CanConvert(Type objectType)
{
var genericTypedef = objectType.GetGenericTypeDefinition();
if (genericTypedef != typeof(Dictionary<,>) && genericTypedef != typeof(ConcurrentDictionary<,>)) return false;
if (objectType.GenericTypeArguments[0] != typeof(ulong)) return false;
var valueParam = objectType.GenericTypeArguments[1];
return typeof(SnowflakeObject).GetTypeInfo().IsAssignableFrom(valueParam.GetTypeInfo()) ||
valueParam == typeof(DiscordVoiceState);
}
}
diff --git a/DisCatSharp/Net/Udp/BaseUdpClient.cs b/DisCatSharp/Net/Udp/BaseUdpClient.cs
index 1d397143c..f80dd971a 100644
--- a/DisCatSharp/Net/Udp/BaseUdpClient.cs
+++ b/DisCatSharp/Net/Udp/BaseUdpClient.cs
@@ -1,62 +1,62 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Threading.Tasks;
namespace DisCatSharp.Net.Udp;
/// <summary>
/// Creates an instance of a UDP client implementation.
/// </summary>
/// <returns>Constructed UDP client implementation.</returns>
public delegate BaseUdpClient UdpClientFactoryDelegate();
/// <summary>
/// Represents a base abstraction for all UDP client implementations.
/// </summary>
public abstract class BaseUdpClient
{
/// <summary>
/// Configures the UDP client.
/// </summary>
/// <param name="endpoint">Endpoint that the client will be communicating with.</param>
public abstract void Setup(ConnectionEndpoint endpoint);
/// <summary>
/// Sends a datagram.
/// </summary>
/// <param name="data">Datagram.</param>
/// <param name="dataLength">Length of the datagram.</param>
/// <returns></returns>
public abstract Task SendAsync(byte[] data, int dataLength);
/// <summary>
/// Receives a datagram.
/// </summary>
/// <returns>The received bytes.</returns>
public abstract Task<byte[]> ReceiveAsync();
/// <summary>
/// Closes and disposes the client.
/// </summary>
public abstract void Close();
}
diff --git a/DisCatSharp/Net/Udp/DCSUdpClient.cs b/DisCatSharp/Net/Udp/DCSUdpClient.cs
index da18677db..492910e93 100644
--- a/DisCatSharp/Net/Udp/DCSUdpClient.cs
+++ b/DisCatSharp/Net/Udp/DCSUdpClient.cs
@@ -1,144 +1,144 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
/// <summary>
/// The default, native-based UDP client implementation.
/// </summary>
internal class DcsUdpClient : BaseUdpClient
{
/// <summary>
/// Gets the client.
/// </summary>
private UdpClient _client;
/// <summary>
/// Gets the end point.
/// </summary>
private ConnectionEndpoint _endPoint;
/// <summary>
/// Gets the packet queue.
/// </summary>
private readonly BlockingCollection<byte[]> _packetQueue;
/// <summary>
/// Gets the receiver task.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0052:Remove unread private members",
Justification = "<Pending>")]
private Task _receiverTask;
/// <summary>
/// Gets the cancellation token source.
/// </summary>
private readonly CancellationTokenSource _tokenSource;
/// <summary>
/// Gets the cancellation token.
/// </summary>
private CancellationToken TOKEN => this._tokenSource.Token;
/// <summary>
/// Creates a new UDP client instance.
/// </summary>
public DcsUdpClient()
{
this._packetQueue = new BlockingCollection<byte[]>();
this._tokenSource = new CancellationTokenSource();
}
/// <summary>
/// Configures the UDP client.
/// </summary>
/// <param name="endpoint">Endpoint that the client will be communicating with.</param>
public override void Setup(ConnectionEndpoint endpoint)
{
this._endPoint = endpoint;
this._client = new UdpClient();
this._receiverTask = Task.Run(this.ReceiverLoopAsync, this.TOKEN);
}
/// <summary>
/// Sends a datagram.
/// </summary>
/// <param name="data">Datagram.</param>
/// <param name="dataLength">Length of the datagram.</param>
/// <returns></returns>
public override Task SendAsync(byte[] data, int dataLength)
=> this._client.SendAsync(data, dataLength, this._endPoint.Hostname, this._endPoint.Port);
/// <summary>
/// Receives a datagram.
/// </summary>
/// <returns>The received bytes.</returns>
public override Task<byte[]> ReceiveAsync() => Task.FromResult(this._packetQueue.Take(this.TOKEN));
/// <summary>
/// Closes and disposes the client.
/// </summary>
public override void Close()
{
this._tokenSource.Cancel();
try
{
this._client.Close();
}
catch (Exception)
{
// Nothing
}
// dequeue all the packets
this._packetQueue.Dispose();
}
/// <summary>
/// Receivers the loop.
/// </summary>
private async Task ReceiverLoopAsync()
{
while (!this.TOKEN.IsCancellationRequested)
{
try
{
var packet = await this._client.ReceiveAsync().ConfigureAwait(false);
this._packetQueue.Add(packet.Buffer);
}
catch (Exception) { }
}
}
/// <summary>
/// Creates a new instance of <see cref="BaseUdpClient"/>.
/// </summary>
public static BaseUdpClient CreateNew()
=> new DcsUdpClient();
}
diff --git a/DisCatSharp/Net/WebSocket/IWebSocketClient.cs b/DisCatSharp/Net/WebSocket/IWebSocketClient.cs
index 350e11b6b..96cfb710a 100644
--- a/DisCatSharp/Net/WebSocket/IWebSocketClient.cs
+++ b/DisCatSharp/Net/WebSocket/IWebSocketClient.cs
@@ -1,114 +1,114 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Net;
using System.Threading.Tasks;
using DisCatSharp.Common.Utilities;
using DisCatSharp.EventArgs;
namespace DisCatSharp.Net.WebSocket;
/// <summary>
/// Creates an instance of a WebSocket client implementation.
/// </summary>
/// <param name="proxy">Proxy settings to use for the new WebSocket client instance.</param>
/// <param name="provider">Service provider.</param>
/// <returns>Constructed WebSocket client implementation.</returns>
public delegate IWebSocketClient WebSocketClientFactoryDelegate(IWebProxy proxy, IServiceProvider provider);
/// <summary>
/// Represents a base abstraction for all WebSocket client implementations.
/// </summary>
public interface IWebSocketClient : IDisposable
{
/// <summary>
/// Gets the proxy settings for this client.
/// </summary>
IWebProxy Proxy { get; }
/// <summary>
/// Gets the collection of default headers to send when connecting to the remote endpoint.
/// </summary>
IReadOnlyDictionary<string, string> DefaultHeaders { get; }
/// <summary>
/// <para>Gets the service provider.</para>
/// </summary>
internal IServiceProvider ServiceProvider { get; set; }
/// <summary>
/// Connects to a specified remote WebSocket endpoint.
/// </summary>
/// <param name="uri">The URI of the WebSocket endpoint.</param>
Task ConnectAsync(Uri uri);
/// <summary>
/// Disconnects the WebSocket connection.
/// </summary>
/// <param name="code">The code.</param>
/// <param name="message">The message.</param>
Task DisconnectAsync(int code = 1000, string message = "");
/// <summary>
/// Send a message to the WebSocket server.
/// </summary>
/// <param name="message">The message to send.</param>
Task SendMessageAsync(string message);
/// <summary>
/// Adds a header to the default header collection.
/// </summary>
/// <param name="name">Name of the header to add.</param>
/// <param name="value">Value of the header to add.</param>
/// <returns>Whether the operation succeeded.</returns>
bool AddDefaultHeader(string name, string value);
/// <summary>
/// Removes a header from the default header collection.
/// </summary>
/// <param name="name">Name of the header to remove.</param>
/// <returns>Whether the operation succeeded.</returns>
bool RemoveDefaultHeader(string name);
/// <summary>
/// Triggered when the client connects successfully.
/// </summary>
event AsyncEventHandler<IWebSocketClient, SocketEventArgs> Connected;
/// <summary>
/// Triggered when the client is disconnected.
/// </summary>
event AsyncEventHandler<IWebSocketClient, SocketCloseEventArgs> Disconnected;
/// <summary>
/// Triggered when the client receives a message from the remote party.
/// </summary>
event AsyncEventHandler<IWebSocketClient, SocketMessageEventArgs> MessageReceived;
/// <summary>
/// Triggered when an error occurs in the client.
/// </summary>
event AsyncEventHandler<IWebSocketClient, SocketErrorEventArgs> ExceptionThrown;
}
diff --git a/DisCatSharp/Net/WebSocket/PayloadDecompressor.cs b/DisCatSharp/Net/WebSocket/PayloadDecompressor.cs
index 1a90f4099..70f786ad1 100644
--- a/DisCatSharp/Net/WebSocket/PayloadDecompressor.cs
+++ b/DisCatSharp/Net/WebSocket/PayloadDecompressor.cs
@@ -1,130 +1,130 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.IO;
using System.IO.Compression;
using DisCatSharp.Enums;
namespace DisCatSharp.Net.WebSocket;
/// <summary>
/// Represents a payload decompressor.
/// </summary>
internal sealed class PayloadDecompressor : IDisposable
{
/// <summary>
/// The zlib flush.
/// </summary>
private const uint ZLIB_FLUSH = 0x0000FFFF;
/// <summary>
/// The zlib prefix.
/// </summary>
private const byte ZLIB_PREFIX = 0x78;
/// <summary>
/// Gets the compression level.
/// </summary>
public GatewayCompressionLevel CompressionLevel { get; }
/// <summary>
/// Gets the compressed stream.
/// </summary>
private readonly MemoryStream _compressedStream;
/// <summary>
/// Gets the decompressor stream.
/// </summary>
private readonly DeflateStream _decompressorStream;
/// <summary>
/// Initializes a new instance of the <see cref="PayloadDecompressor"/> class.
/// </summary>
/// <param name="compressionLevel">The compression level.</param>
public PayloadDecompressor(GatewayCompressionLevel compressionLevel)
{
if (compressionLevel == GatewayCompressionLevel.None)
throw new InvalidOperationException("Decompressor requires a valid compression mode.");
this.CompressionLevel = compressionLevel;
this._compressedStream = new MemoryStream();
if (this.CompressionLevel == GatewayCompressionLevel.Stream)
this._decompressorStream = new DeflateStream(this._compressedStream, CompressionMode.Decompress);
}
/// <summary>
/// Tries the decompress.
/// </summary>
/// <param name="compressed">The compressed bytes.</param>
/// <param name="decompressed">The decompressed memory stream.</param>
public bool TryDecompress(ArraySegment<byte> compressed, MemoryStream decompressed)
{
var zlib = this.CompressionLevel == GatewayCompressionLevel.Stream
? this._decompressorStream
: new DeflateStream(this._compressedStream, CompressionMode.Decompress, true);
if (compressed.Array[0] == ZLIB_PREFIX)
this._compressedStream.Write(compressed.Array, compressed.Offset + 2, compressed.Count - 2);
else
this._compressedStream.Write(compressed.Array, compressed.Offset, compressed.Count);
this._compressedStream.Flush();
this._compressedStream.Position = 0;
var cspan = compressed.AsSpan();
var suffix = BinaryPrimitives.ReadUInt32BigEndian(cspan[^4..]);
if (this.CompressionLevel == GatewayCompressionLevel.Stream && suffix != ZLIB_FLUSH)
{
if (this.CompressionLevel == GatewayCompressionLevel.Payload)
zlib.Dispose();
return false;
}
try
{
zlib.CopyTo(decompressed);
return true;
}
catch { return false; }
finally
{
this._compressedStream.Position = 0;
this._compressedStream.SetLength(0);
if (this.CompressionLevel == GatewayCompressionLevel.Payload)
zlib.Dispose();
}
}
/// <summary>
/// Disposes the decompressor.
/// </summary>
public void Dispose()
{
this._decompressorStream?.Dispose();
this._compressedStream.Dispose();
}
}
diff --git a/DisCatSharp/Net/WebSocket/SocketLock.cs b/DisCatSharp/Net/WebSocket/SocketLock.cs
index 2f7c2d492..7e8d51ad7 100644
--- a/DisCatSharp/Net/WebSocket/SocketLock.cs
+++ b/DisCatSharp/Net/WebSocket/SocketLock.cs
@@ -1,138 +1,138 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
namespace DisCatSharp.Net.WebSocket;
// Licensed from Clyde.NET
/// <summary>
/// Represents a socket lock.
/// </summary>
internal sealed class SocketLock : IDisposable
{
/// <summary>
/// Gets the application id.
/// </summary>
public ulong ApplicationId { get; }
/// <summary>
/// Gets the lock semaphore.
/// </summary>
private readonly SemaphoreSlim _lockSemaphore;
/// <summary>
/// Gets or sets the timeout cancel source.
/// </summary>
private CancellationTokenSource _timeoutCancelSource;
/// <summary>
/// Gets the cancel token.
/// </summary>
private CancellationToken TIMEOUT_CANCEL => this._timeoutCancelSource.Token;
/// <summary>
/// Gets or sets the unlock task.
/// </summary>
private Task _unlockTask;
/// <summary>
/// Gets or sets the max concurrency.
/// </summary>
private readonly int _maxConcurrency;
/// <summary>
/// Initializes a new instance of the <see cref="SocketLock"/> class.
/// </summary>
/// <param name="appId">The app id.</param>
/// <param name="maxConcurrency">The max concurrency.</param>
public SocketLock(ulong appId, int maxConcurrency)
{
this.ApplicationId = appId;
this._timeoutCancelSource = null;
this._maxConcurrency = maxConcurrency;
this._lockSemaphore = new SemaphoreSlim(maxConcurrency);
}
/// <summary>
/// Locks the socket.
/// </summary>
public async Task LockAsync()
{
await this._lockSemaphore.WaitAsync().ConfigureAwait(false);
this._timeoutCancelSource = new CancellationTokenSource();
this._unlockTask = Task.Delay(TimeSpan.FromSeconds(30), this.TIMEOUT_CANCEL);
_ = this._unlockTask.ContinueWith(this.InternalUnlock, TaskContinuationOptions.NotOnCanceled);
}
/// <summary>
/// Unlocks the socket after a given timespan.
/// </summary>
/// <param name="unlockDelay">The unlock delay.</param>
public void UnlockAfter(TimeSpan unlockDelay)
{
if (this._timeoutCancelSource == null || this._lockSemaphore.CurrentCount > 0)
return; // it's not unlockable because it's post-IDENTIFY or not locked
try
{
this._timeoutCancelSource.Cancel();
this._timeoutCancelSource.Dispose();
}
catch { }
this._timeoutCancelSource = null;
this._unlockTask = Task.Delay(unlockDelay, CancellationToken.None);
_ = this._unlockTask.ContinueWith(this.InternalUnlock);
}
/// <summary>
/// Waits for the socket lock.
/// </summary>
/// <returns>A Task.</returns>
public Task WaitAsync()
=> this._lockSemaphore.WaitAsync();
/// <summary>
/// Disposes the socket lock.
/// </summary>
public void Dispose()
{
try
{
this._timeoutCancelSource?.Cancel();
this._timeoutCancelSource?.Dispose();
}
catch { }
}
/// <summary>
/// Unlocks the socket.
/// </summary>
/// <param name="t">The task.</param>
private void InternalUnlock(Task t)
=> this._lockSemaphore.Release(this._maxConcurrency);
}
diff --git a/DisCatSharp/Net/WebSocket/WebSocketClient.cs b/DisCatSharp/Net/WebSocket/WebSocketClient.cs
index 3db19f300..6c9367925 100644
--- a/DisCatSharp/Net/WebSocket/WebSocketClient.cs
+++ b/DisCatSharp/Net/WebSocket/WebSocketClient.cs
@@ -1,416 +1,416 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.IO;
using System.Net;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using DisCatSharp.Common.Utilities;
using DisCatSharp.EventArgs;
namespace DisCatSharp.Net.WebSocket;
// weebsocket
// not even sure whether emzi or I posted this. much love, naam.
/// <summary>
/// The default, native-based WebSocket client implementation.
/// </summary>
public class WebSocketClient : IWebSocketClient
{
/// <summary>
/// The outgoing chunk size.
/// </summary>
private const int OUTGOING_CHUNK_SIZE = 8192; // 8 KiB
/// <summary>
/// The incoming chunk size.
/// </summary>
private const int INCOMING_CHUNK_SIZE = 32768; // 32 KiB
/// <summary>
/// Gets the proxy settings for this client.
/// </summary>
public IWebProxy Proxy { get; }
/// <summary>
/// Gets the collection of default headers to send when connecting to the remote endpoint.
/// </summary>
public IReadOnlyDictionary<string, string> DefaultHeaders { get; }
/// <summary>
/// Gets or sets the service provider.
/// </summary>
IServiceProvider IWebSocketClient.ServiceProvider
{
get => this._serviceProvider;
set => this._serviceProvider = value;
}
private readonly Dictionary<string, string> _defaultHeaders;
private Task _receiverTask;
private CancellationTokenSource _receiverTokenSource;
private CancellationToken _receiverToken;
private readonly SemaphoreSlim _senderLock;
private CancellationTokenSource _socketTokenSource;
private CancellationToken _socketToken;
private ClientWebSocket _ws;
private volatile bool _isClientClose;
private volatile bool _isConnected;
private bool _isDisposed;
/// <summary>
/// Instantiates a new WebSocket client with specified proxy settings.
/// </summary>
/// <param name="proxy">Proxy settings for the client.</param>
/// <param name="provider">Service provider.</param>
private WebSocketClient(IWebProxy proxy, IServiceProvider provider)
{
this._connected = new AsyncEvent<WebSocketClient, SocketEventArgs>("WS_CONNECT", TimeSpan.Zero, this.EventErrorHandler);
this._disconnected = new AsyncEvent<WebSocketClient, SocketCloseEventArgs>("WS_DISCONNECT", TimeSpan.Zero, this.EventErrorHandler);
this._messageReceived = new AsyncEvent<WebSocketClient, SocketMessageEventArgs>("WS_MESSAGE", TimeSpan.Zero, this.EventErrorHandler);
this._exceptionThrown = new AsyncEvent<WebSocketClient, SocketErrorEventArgs>("WS_ERROR", TimeSpan.Zero, null);
this.Proxy = proxy;
this._defaultHeaders = new Dictionary<string, string>();
this.DefaultHeaders = new ReadOnlyDictionary<string, string>(this._defaultHeaders);
this._receiverTokenSource = null;
this._receiverToken = CancellationToken.None;
this._senderLock = new SemaphoreSlim(1);
this._socketTokenSource = null;
this._socketToken = CancellationToken.None;
this._serviceProvider = provider;
}
/// <summary>
/// Connects to a specified remote WebSocket endpoint.
/// </summary>
/// <param name="uri">The URI of the WebSocket endpoint.</param>
public async Task ConnectAsync(Uri uri)
{
// Disconnect first
try { await this.DisconnectAsync().ConfigureAwait(false); } catch { }
// Disallow sending messages
await this._senderLock.WaitAsync().ConfigureAwait(false);
try
{
// This can be null at this point
this._receiverTokenSource?.Dispose();
this._socketTokenSource?.Dispose();
this._ws?.Dispose();
this._ws = new ClientWebSocket();
this._ws.Options.Proxy = this.Proxy;
this._ws.Options.KeepAliveInterval = TimeSpan.Zero;
if (this._defaultHeaders != null)
foreach (var (k, v) in this._defaultHeaders)
this._ws.Options.SetRequestHeader(k, v);
this._receiverTokenSource = new CancellationTokenSource();
this._receiverToken = this._receiverTokenSource.Token;
this._socketTokenSource = new CancellationTokenSource();
this._socketToken = this._socketTokenSource.Token;
this._isClientClose = false;
this._isDisposed = false;
await this._ws.ConnectAsync(uri, this._socketToken).ConfigureAwait(false);
this._receiverTask = Task.Run(this.ReceiverLoopAsync, this._receiverToken);
}
finally
{
this._senderLock.Release();
}
}
/// <summary>
/// Disconnects the WebSocket connection.
/// </summary>
/// <param name="code">The code</param>
/// <param name="message">The message</param>
/// <created>Lala Sabathil,06.07.2021</created>
/// <changed>Lala Sabathil,06.07.2021</changed>
public async Task DisconnectAsync(int code = 1000, string message = "")
{
// Ensure that messages cannot be sent
await this._senderLock.WaitAsync().ConfigureAwait(false);
try
{
this._isClientClose = true;
if (this._ws != null && (this._ws.State == WebSocketState.Open || this._ws.State == WebSocketState.CloseReceived))
await this._ws.CloseOutputAsync((WebSocketCloseStatus)code, message, CancellationToken.None).ConfigureAwait(false);
if (this._receiverTask != null)
await this._receiverTask.ConfigureAwait(false); // Ensure that receiving completed
if (this._isConnected)
this._isConnected = false;
if (!this._isDisposed)
{
// Cancel all running tasks
if (this._socketToken.CanBeCanceled)
this._socketTokenSource?.Cancel();
this._socketTokenSource?.Dispose();
if (this._receiverToken.CanBeCanceled)
this._receiverTokenSource?.Cancel();
this._receiverTokenSource?.Dispose();
this._isDisposed = true;
}
}
catch { }
finally
{
this._senderLock.Release();
}
}
/// <summary>
/// Send a message to the WebSocket server.
/// </summary>
/// <param name="message">The message to send.</param>
public async Task SendMessageAsync(string message)
{
if (this._ws == null)
return;
if (this._ws.State != WebSocketState.Open && this._ws.State != WebSocketState.CloseReceived)
return;
var bytes = Utilities.UTF8.GetBytes(message);
await this._senderLock.WaitAsync().ConfigureAwait(false);
try
{
var len = bytes.Length;
var segCount = len / OUTGOING_CHUNK_SIZE;
if (len % OUTGOING_CHUNK_SIZE != 0)
segCount++;
for (var i = 0; i < segCount; i++)
{
var segStart = OUTGOING_CHUNK_SIZE * i;
var segLen = Math.Min(OUTGOING_CHUNK_SIZE, len - segStart);
await this._ws.SendAsync(new ArraySegment<byte>(bytes, segStart, segLen), WebSocketMessageType.Text, i == segCount - 1, CancellationToken.None).ConfigureAwait(false);
}
}
finally
{
this._senderLock.Release();
}
}
/// <summary>
/// Adds a header to the default header collection.
/// </summary>
/// <param name="name">Name of the header to add.</param>
/// <param name="value">Value of the header to add.</param>
/// <returns>Whether the operation succeeded.</returns>
public bool AddDefaultHeader(string name, string value)
{
this._defaultHeaders[name] = value;
return true;
}
/// <summary>
/// Removes a header from the default header collection.
/// </summary>
/// <param name="name">Name of the header to remove.</param>
/// <returns>Whether the operation succeeded.</returns>
public bool RemoveDefaultHeader(string name)
=> this._defaultHeaders.Remove(name);
/// <summary>
/// Disposes of resources used by this WebSocket client instance.
/// </summary>
public void Dispose()
{
if (this._isDisposed)
return;
this._isDisposed = true;
this.DisconnectAsync().ConfigureAwait(false).GetAwaiter().GetResult();
this._receiverTokenSource?.Dispose();
this._socketTokenSource?.Dispose();
GC.SuppressFinalize(this);
}
/// <summary>
/// Receivers the loop.
/// </summary>
internal async Task ReceiverLoopAsync()
{
await Task.Yield();
var token = this._receiverToken;
var buffer = new ArraySegment<byte>(new byte[INCOMING_CHUNK_SIZE]);
try
{
using var bs = new MemoryStream();
while (!token.IsCancellationRequested)
{
// See https://github.com/RogueException/Discord.Net/commit/ac389f5f6823e3a720aedd81b7805adbdd78b66d
// for explanation on the cancellation token
WebSocketReceiveResult result;
byte[] resultBytes;
do
{
result = await this._ws.ReceiveAsync(buffer, CancellationToken.None).ConfigureAwait(false);
if (result.MessageType == WebSocketMessageType.Close)
break;
bs.Write(buffer.Array, 0, result.Count);
}
while (!result.EndOfMessage);
resultBytes = new byte[bs.Length];
bs.Position = 0;
bs.Read(resultBytes, 0, resultBytes.Length);
bs.Position = 0;
bs.SetLength(0);
if (!this._isConnected && result.MessageType != WebSocketMessageType.Close)
{
this._isConnected = true;
await this._connected.InvokeAsync(this, new SocketEventArgs(this._serviceProvider)).ConfigureAwait(false);
}
if (result.MessageType == WebSocketMessageType.Binary)
{
await this._messageReceived.InvokeAsync(this, new SocketBinaryMessageEventArgs(resultBytes)).ConfigureAwait(false);
}
else if (result.MessageType == WebSocketMessageType.Text)
{
await this._messageReceived.InvokeAsync(this, new SocketTextMessageEventArgs(Utilities.UTF8.GetString(resultBytes))).ConfigureAwait(false);
}
else // close
{
if (!this._isClientClose)
{
var code = result.CloseStatus.Value;
code = code == WebSocketCloseStatus.NormalClosure || code == WebSocketCloseStatus.EndpointUnavailable
? (WebSocketCloseStatus)4000
: code;
await this._ws.CloseOutputAsync(code, result.CloseStatusDescription, CancellationToken.None).ConfigureAwait(false);
}
await this._disconnected.InvokeAsync(this, new SocketCloseEventArgs(this._serviceProvider) { CloseCode = (int)result.CloseStatus, CloseMessage = result.CloseStatusDescription }).ConfigureAwait(false);
break;
}
}
}
catch (Exception ex)
{
await this._exceptionThrown.InvokeAsync(this, new SocketErrorEventArgs(this._serviceProvider) { Exception = ex }).ConfigureAwait(false);
await this._disconnected.InvokeAsync(this, new SocketCloseEventArgs(this._serviceProvider) { CloseCode = -1, CloseMessage = "" }).ConfigureAwait(false);
}
// Don't await or you deadlock
// DisconnectAsync waits for this method
_ = this.DisconnectAsync().ConfigureAwait(false);
}
/// <summary>
/// Creates a new instance of <see cref="WebSocketClient"/>.
/// </summary>
/// <param name="proxy">Proxy to use for this client instance.</param>
/// <param name="provider">Service provider.</param>
/// <returns>An instance of <see cref="WebSocketClient"/>.</returns>
public static IWebSocketClient CreateNew(IWebProxy proxy, IServiceProvider provider)
=> new WebSocketClient(proxy, provider);
#region Events
/// <summary>
/// Triggered when the client connects successfully.
/// </summary>
public event AsyncEventHandler<IWebSocketClient, SocketEventArgs> Connected
{
add => this._connected.Register(value);
remove => this._connected.Unregister(value);
}
private readonly AsyncEvent<WebSocketClient, SocketEventArgs> _connected;
/// <summary>
/// Triggered when the client is disconnected.
/// </summary>
public event AsyncEventHandler<IWebSocketClient, SocketCloseEventArgs> Disconnected
{
add => this._disconnected.Register(value);
remove => this._disconnected.Unregister(value);
}
private readonly AsyncEvent<WebSocketClient, SocketCloseEventArgs> _disconnected;
/// <summary>
/// Triggered when the client receives a message from the remote party.
/// </summary>
public event AsyncEventHandler<IWebSocketClient, SocketMessageEventArgs> MessageReceived
{
add => this._messageReceived.Register(value);
remove => this._messageReceived.Unregister(value);
}
private readonly AsyncEvent<WebSocketClient, SocketMessageEventArgs> _messageReceived;
/// <summary>
/// Triggered when an error occurs in the client.
/// </summary>
public event AsyncEventHandler<IWebSocketClient, SocketErrorEventArgs> ExceptionThrown
{
add => this._exceptionThrown.Register(value);
remove => this._exceptionThrown.Unregister(value);
}
private readonly AsyncEvent<WebSocketClient, SocketErrorEventArgs> _exceptionThrown;
private IServiceProvider _serviceProvider;
/// <summary>
/// Events the error handler.
/// </summary>
/// <param name="asyncEvent">The event.</param>
/// <param name="ex">The exception.</param>
/// <param name="handler">The handler.</param>
/// <param name="sender">The sender.</param>
/// <param name="eventArgs">The event args.</param>
private void EventErrorHandler<TArgs>(AsyncEvent<WebSocketClient, TArgs> asyncEvent, Exception ex, AsyncEventHandler<WebSocketClient, TArgs> handler, WebSocketClient sender, TArgs eventArgs)
where TArgs : AsyncEventArgs
=> this._exceptionThrown.InvokeAsync(this, new SocketErrorEventArgs(this._serviceProvider) { Exception = ex }).ConfigureAwait(false).GetAwaiter().GetResult();
#endregion
}
diff --git a/DisCatSharp/Properties/AssemblyProperties.cs b/DisCatSharp/Properties/AssemblyProperties.cs
index 873e336fc..de8723399 100644
--- a/DisCatSharp/Properties/AssemblyProperties.cs
+++ b/DisCatSharp/Properties/AssemblyProperties.cs
@@ -1,46 +1,46 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("DisCatSharp.ApplicationCommands")]
[assembly: InternalsVisibleTo("DisCatSharp.CommandsNext")]
[assembly: InternalsVisibleTo("DisCatSharp.Common")]
[assembly: InternalsVisibleTo("DisCatSharp.Configuration")]
[assembly: InternalsVisibleTo("DisCatSharp.Configuration.Tests")]
[assembly: InternalsVisibleTo("DisCatSharp.Hosting")]
[assembly: InternalsVisibleTo("DisCatSharp.Hosting.DependencyInjection")]
[assembly: InternalsVisibleTo("DisCatSharp.Hosting.Tests")]
[assembly: InternalsVisibleTo("DisCatSharp.Interactivity")]
[assembly: InternalsVisibleTo("DisCatSharp.Lavalink")]
[assembly: InternalsVisibleTo("DisCatSharp.Phabricator")]
[assembly: InternalsVisibleTo("DisCatSharp.Support")]
[assembly: InternalsVisibleTo("DisCatSharp.Test")]
[assembly: InternalsVisibleTo("DisCatSharp.VoiceNext")]
[assembly: InternalsVisibleTo("DisCatSharp.VoiceNext.Natives")]
[assembly: InternalsVisibleTo("Nyaw")]
[assembly: InternalsVisibleTo("DisCatSharp.DevTools")]
[assembly: InternalsVisibleTo("DisCatSharp.DocsGenerator")]
[assembly: InternalsVisibleTo("DisCatSharp.StaffApps")]
[assembly: InternalsVisibleTo("Microsoft.DocAsCode")]
[assembly: InternalsVisibleTo("Microsoft.DocAsCode.Metadata.ManagedReference")]
[assembly: InternalsVisibleTo("DisCatSharp.Experimental")]
diff --git a/DisCatSharp/QueryUriBuilder.cs b/DisCatSharp/QueryUriBuilder.cs
index a36a189ba..14a6b25cc 100644
--- a/DisCatSharp/QueryUriBuilder.cs
+++ b/DisCatSharp/QueryUriBuilder.cs
@@ -1,93 +1,93 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
namespace DisCatSharp;
/// <summary>
/// Represents a query uri builder.
/// </summary>
internal class QueryUriBuilder
{
/// <summary>
/// Gets the source uri.
/// </summary>
public Uri SourceUri { get; }
/// <summary>
/// Gets the query parameters.
/// </summary>
public IReadOnlyList<KeyValuePair<string, string>> QueryParameters => this._queryParams;
private readonly List<KeyValuePair<string, string>> _queryParams = new();
/// <summary>
/// Initializes a new instance of the <see cref="QueryUriBuilder"/> class.
/// </summary>
/// <param name="uri">The uri.</param>
public QueryUriBuilder(string uri)
{
if (uri == null)
throw new ArgumentNullException(nameof(uri));
this.SourceUri = new Uri(uri);
}
/// <summary>
/// Initializes a new instance of the <see cref="QueryUriBuilder"/> class.
/// </summary>
/// <param name="uri">The uri.</param>
public QueryUriBuilder(Uri uri)
{
if (uri == null)
throw new ArgumentNullException(nameof(uri));
this.SourceUri = uri;
}
/// <summary>
/// Adds a parameter.
/// </summary>
/// <param name="key">The key to be added.</param>
/// <param name="value">The value to be added.</param>
public QueryUriBuilder AddParameter(string key, string value)
{
this._queryParams.Add(new KeyValuePair<string, string>(key, value));
return this;
}
/// <summary>
/// Builds the uri.
/// </summary>
public Uri Build() =>
new UriBuilder(this.SourceUri)
{
Query = string.Join("&", this._queryParams.Select(e => Uri.EscapeDataString(e.Key) + '=' + Uri.EscapeDataString(e.Value)))
}.Uri;
/// <summary>
/// Returns a readable string.
/// </summary>
public override string ToString() => this.Build().ToString();
}
diff --git a/DisCatSharp/ReadOnlyConcurrentDictionary.cs b/DisCatSharp/ReadOnlyConcurrentDictionary.cs
index 98b9c449a..5bd63c133 100644
--- a/DisCatSharp/ReadOnlyConcurrentDictionary.cs
+++ b/DisCatSharp/ReadOnlyConcurrentDictionary.cs
@@ -1,95 +1,95 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace DisCatSharp;
/// <summary>
/// Read-only view of a given <see cref="ConcurrentDictionary{TKey,TValue}"/>.
/// </summary>
/// <remarks>
/// This type exists because <see cref="ConcurrentDictionary{TKey,TValue}"/> is not an
/// <see cref="IReadOnlyDictionary{TKey,TValue}"/> in .NET Standard 1.1.
/// </remarks>
/// <typeparam name="TKey">The type of keys in the dictionary.</typeparam>
/// <typeparam name="TValue">The type of values in the dictionary.</typeparam>
internal readonly struct ReadOnlyConcurrentDictionary<TKey, TValue> : IReadOnlyDictionary<TKey, TValue>
{
private readonly ConcurrentDictionary<TKey, TValue> _underlyingDict;
/// <summary>
/// Creates a new read-only view of the given dictionary.
/// </summary>
/// <param name="underlyingDict">Dictionary to create a view over.</param>
public ReadOnlyConcurrentDictionary(ConcurrentDictionary<TKey, TValue> underlyingDict)
{
this._underlyingDict = underlyingDict;
}
/// <summary>
/// Gets the enumerator.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => this._underlyingDict.GetEnumerator();
/// <summary>
/// Gets the enumerator.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this._underlyingDict).GetEnumerator();
/// <summary>
/// Gets the count.
/// </summary>
public int Count => this._underlyingDict.Count;
/// <summary>
/// Contains the key.
/// </summary>
/// <param name="key">The key.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool ContainsKey(TKey key) => this._underlyingDict.ContainsKey(key);
/// <summary>
/// Tries the get value.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="value">The value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetValue(TKey key, out TValue value) => this._underlyingDict.TryGetValue(key, out value);
public TValue this[TKey key] => this._underlyingDict[key];
/// <summary>
/// Gets the keys.
/// </summary>
public IEnumerable<TKey> Keys => this._underlyingDict.Keys;
/// <summary>
/// Gets the values.
/// </summary>
public IEnumerable<TValue> Values => this._underlyingDict.Values;
}
diff --git a/DisCatSharp/ReadOnlySet.cs b/DisCatSharp/ReadOnlySet.cs
index bdbd167f7..8a791e417 100644
--- a/DisCatSharp/ReadOnlySet.cs
+++ b/DisCatSharp/ReadOnlySet.cs
@@ -1,66 +1,66 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace DisCatSharp;
/// <summary>
/// Read-only view of a given <see cref="ISet{T}"/>.
/// </summary>
/// <typeparam name="T">Type of the items in the set.</typeparam>
internal readonly struct ReadOnlySet<T> : IReadOnlyCollection<T>
{
private readonly ISet<T> _underlyingSet;
/// <summary>
/// Creates a new read-only view of the given set.
/// </summary>
/// <param name="sourceSet">Set to create a view over.</param>
public ReadOnlySet(ISet<T> sourceSet)
{
this._underlyingSet = sourceSet;
}
/// <summary>
/// Gets the number of items in the underlying set.
/// </summary>
public int Count => this._underlyingSet.Count;
/// <summary>
/// Returns an enumerator that iterates through this set view.
/// </summary>
/// <returns>Enumerator for the underlying set.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IEnumerator<T> GetEnumerator()
=> this._underlyingSet.GetEnumerator();
/// <summary>
/// Returns an enumerator that iterates through this set view.
/// </summary>
/// <returns>Enumerator for the underlying set.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
IEnumerator IEnumerable.GetEnumerator()
=> (this._underlyingSet as IEnumerable).GetEnumerator();
}
diff --git a/DisCatSharp/RingBuffer.cs b/DisCatSharp/RingBuffer.cs
index fd0f7addf..b3e2aaa30 100644
--- a/DisCatSharp/RingBuffer.cs
+++ b/DisCatSharp/RingBuffer.cs
@@ -1,238 +1,238 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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;
using DisCatSharp.Common;
namespace DisCatSharp;
/// <summary>
/// A circular buffer collection.
/// </summary>
/// <typeparam name="T">Type of elements within this ring buffer.</typeparam>
public class RingBuffer<T> : ICollection<T>
{
/// <summary>
/// Gets the current index of the buffer items.
/// </summary>
public int CurrentIndex { get; protected set; }
/// <summary>
/// Gets the capacity of this ring buffer.
/// </summary>
public int Capacity { get; protected set; }
/// <summary>
/// Gets the number of items in this ring buffer.
/// </summary>
public int Count
=> this._reachedEnd ? this.Capacity : this.CurrentIndex;
/// <summary>
/// Gets whether this ring buffer is read-only.
/// </summary>
public bool IsReadOnly
=> false;
/// <summary>
/// Gets or sets the internal collection of items.
/// </summary>
protected T[] InternalBuffer { get; set; }
private bool _reachedEnd;
/// <summary>
/// Creates a new ring buffer with specified size.
/// </summary>
/// <param name="size">Size of the buffer to create.</param>
/// <exception cref="ArgumentOutOfRangeException" />
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];
}
/// <summary>
/// Creates a new ring buffer, filled with specified elements.
/// </summary>
/// <param name="elements">Elements to fill the buffer with.</param>
/// <exception cref="ArgumentException" />
/// <exception cref="ArgumentOutOfRangeException" />
public RingBuffer(IEnumerable<T> elements)
: this(elements, 0)
{ }
/// <summary>
/// Creates a new ring buffer, filled with specified elements, and starting at specified index.
/// </summary>
/// <param name="elements">Elements to fill the buffer with.</param>
/// <param name="index">Starting element index.</param>
/// <exception cref="ArgumentException" />
/// <exception cref="ArgumentOutOfRangeException" />
public RingBuffer(IEnumerable<T> elements, int index)
{
if (elements == null || !elements.Any())
throw new ArgumentException("The collection cannot be null or empty.", nameof(elements));
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.");
}
/// <summary>
/// Inserts an item into this ring buffer.
/// </summary>
/// <param name="item">Item to insert.</param>
public void Add(T item)
{
this.InternalBuffer[this.CurrentIndex++] = item;
if (this.CurrentIndex == this.Capacity)
{
this.CurrentIndex = 0;
this._reachedEnd = true;
}
}
/// <summary>
/// Gets first item from the buffer that matches the predicate.
/// </summary>
/// <param name="predicate">Predicate used to find the item.</param>
/// <param name="item">Item that matches the predicate, or default value for the type of the items in this ring buffer, if one is not found.</param>
/// <returns>Whether an item that matches the predicate was found or not.</returns>
public bool TryGet(Func<T, bool> 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;
}
/// <summary>
/// Clears this ring buffer and resets the current item index.
/// </summary>
public void Clear()
{
this.InternalBuffer.Populate(default);
this.CurrentIndex = 0;
}
/// <summary>
/// Checks whether given item is present in the buffer. This method is not implemented. Use <see cref="Contains(Func{T, bool})"/> instead.
/// </summary>
/// <param name="item">Item to check for.</param>
/// <returns>Whether the buffer contains the item.</returns>
/// <exception cref="NotImplementedException" />
public bool Contains(T item) => throw new NotImplementedException("This method is not implemented. Use .Contains(predicate) instead.");
/// <summary>
/// Checks whether given item is present in the buffer using given predicate to find it.
/// </summary>
/// <param name="predicate">Predicate used to check for the item.</param>
/// <returns>Whether the buffer contains the item.</returns>
public bool Contains(Func<T, bool> predicate) => this.InternalBuffer.Any(predicate);
/// <summary>
/// Copies this ring buffer to target array, attempting to maintain the order of items within.
/// </summary>
/// <param name="array">Target array.</param>
/// <param name="index">Index starting at which to copy the items to.</param>
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];
}
/// <summary>
/// Removes an item from the buffer. This method is not implemented. Use <see cref="Remove(Func{T, bool})"/> instead.
/// </summary>
/// <param name="item">Item to remove.</param>
/// <returns>Whether an item was removed or not.</returns>
public bool Remove(T item) => throw new NotImplementedException("This method is not implemented. Use .Remove(predicate) instead.");
/// <summary>
/// Removes an item from the buffer using given predicate to find it.
/// </summary>
/// <param name="predicate">Predicate used to find the item.</param>
/// <returns>Whether an item was removed or not.</returns>
public bool Remove(Func<T, bool> 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;
}
/// <summary>
/// Returns an enumerator for this ring buffer.
/// </summary>
/// <returns>Enumerator for this ring buffer.</returns>
public IEnumerator<T> GetEnumerator() =>
!this._reachedEnd
? this.InternalBuffer.AsEnumerable().GetEnumerator()
: this.InternalBuffer.Skip(this.CurrentIndex)
.Concat(this.InternalBuffer.Take(this.CurrentIndex))
.GetEnumerator();
/// <summary>
/// Returns an enumerator for this ring buffer.
/// </summary>
/// <returns>Enumerator for this ring buffer.</returns>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}
diff --git a/DisCatSharp/Utilities.cs b/DisCatSharp/Utilities.cs
index d78622cc6..e5dbbce89 100644
--- a/DisCatSharp/Utilities.cs
+++ b/DisCatSharp/Utilities.cs
@@ -1,482 +1,482 @@
// This file is part of the DisCatSharp project, based off DSharpPlus.
//
-// Copyright (c) 2021-2022 AITSYS
+// Copyright (c) 2021-2023 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.Enums;
using DisCatSharp.Net;
using Microsoft.Extensions.Logging;
namespace DisCatSharp;
/// <summary>
/// Various Discord-related utilities.
/// </summary>
public static class Utilities
{
/// <summary>
/// Gets the version of the library
/// </summary>
internal static string VersionHeader { get; set; }
/// <summary>
/// Gets or sets the permission strings.
/// </summary>
internal static Dictionary<Permissions, string> PermissionStrings { get; set; }
/// <summary>
/// Gets the utf8 encoding
/// </summary>
// ReSharper disable once InconsistentNaming
internal static UTF8Encoding UTF8 { get; } = new(false);
/// <summary>
/// Initializes a new instance of the <see cref="Utilities"/> class.
/// </summary>
static Utilities()
{
PermissionStrings = new Dictionary<Permissions, string>();
var t = typeof(Permissions);
var ti = t.GetTypeInfo();
var vals = Enum.GetValues(t).Cast<Permissions>();
foreach (var xv in vals)
{
var xsv = xv.ToString();
var xmv = ti.DeclaredMembers.FirstOrDefault(xm => xm.Name == xsv);
var xav = xmv.GetCustomAttribute<PermissionStringAttribute>();
PermissionStrings[xv] = xav.String;
}
var a = typeof(DiscordClient).GetTypeInfo().Assembly;
var vs = "";
var iv = a.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
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})";
}
/// <summary>
/// Gets the api base uri.
/// </summary>
/// <param name="config">The config</param>
/// <returns>A string.</returns>
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;
/// <summary>
/// Gets the api uri for.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="config">The config</param>
/// <returns>An Uri.</returns>
internal static Uri GetApiUriFor(string path, DiscordConfiguration config)
=> new($"{GetApiBaseUri(config)}{path}");
/// <summary>
/// Gets the api uri for.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="queryString">The query string.</param>
/// <param name="config">The config</param>
/// <returns>An Uri.</returns>
internal static Uri GetApiUriFor(string path, string queryString, DiscordConfiguration config)
=> new($"{GetApiBaseUri(config)}{path}{queryString}");
/// <summary>
/// Gets the api uri builder for.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="config">The config</param>
/// <returns>A QueryUriBuilder.</returns>
internal static QueryUriBuilder GetApiUriBuilderFor(string path, DiscordConfiguration config)
=> new($"{GetApiBaseUri(config)}{path}");
/// <summary>
/// Gets the formatted token.
/// </summary>
/// <param name="client">The client.</param>
/// <returns>A string.</returns>
internal static string GetFormattedToken(BaseDiscordClient client) => GetFormattedToken(client.Configuration);
/// <summary>
/// Gets the formatted token.
/// </summary>
/// <param name="config">The config.</param>
/// <returns>A string.</returns>
internal static string GetFormattedToken(DiscordConfiguration config) =>
config.TokenType switch
{
TokenType.Bearer => $"Bearer {config.Token}",
TokenType.Bot => $"Bot {config.Token}",
_ => throw new ArgumentException("Invalid token type specified.", nameof(config)),
};
/// <summary>
/// Gets the base headers.
/// </summary>
/// <returns>A Dictionary.</returns>
internal static Dictionary<string, string> GetBaseHeaders()
=> new();
/// <summary>
/// Gets the user agent.
/// </summary>
/// <returns>A string.</returns>
internal static string GetUserAgent()
=> VersionHeader;
/// <summary>
/// Contains the user mentions.
/// </summary>
/// <param name="message">The message.</param>
/// <returns>A bool.</returns>
internal static bool ContainsUserMentions(string message)
{
var pattern = @"<@(\d+)>";
var regex = new Regex(pattern, RegexOptions.ECMAScript);
return regex.IsMatch(message);
}
/// <summary>
/// Contains the nickname mentions.
/// </summary>
/// <param name="message">The message.</param>
/// <returns>A bool.</returns>
internal static bool ContainsNicknameMentions(string message)
{
var pattern = @"<@!(\d+)>";
var regex = new Regex(pattern, RegexOptions.ECMAScript);
return regex.IsMatch(message);
}
/// <summary>
/// Contains the channel mentions.
/// </summary>
/// <param name="message">The message.</param>
/// <returns>A bool.</returns>
internal static bool ContainsChannelMentions(string message)
{
var pattern = @"<#(\d+)>";
var regex = new Regex(pattern, RegexOptions.ECMAScript);
return regex.IsMatch(message);
}
/// <summary>
/// Contains the role mentions.
/// </summary>
/// <param name="message">The message.</param>
/// <returns>A bool.</returns>
internal static bool ContainsRoleMentions(string message)
{
var pattern = @"<@&(\d+)>";
var regex = new Regex(pattern, RegexOptions.ECMAScript);
return regex.IsMatch(message);
}
/// <summary>
/// Contains the emojis.
/// </summary>
/// <param name="message">The message.</param>
/// <returns>A bool.</returns>
internal static bool ContainsEmojis(string message)
{
var pattern = @"<a?:(.*):(\d+)>";
var regex = new Regex(pattern, RegexOptions.ECMAScript);
return regex.IsMatch(message);
}
/// <summary>
/// Gets the user mentions.
/// </summary>
/// <param name="message">The message.</param>
/// <returns>A list of ulong.</returns>
internal static IEnumerable<ulong> GetUserMentions(DiscordMessage message)
{
var regex = new Regex(@"<@!?(\d+)>", RegexOptions.ECMAScript);
var matches = regex.Matches(message.Content);
return from Match match in matches
select ulong.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
}
/// <summary>
/// Gets the role mentions.
/// </summary>
/// <param name="message">The message.</param>
/// <returns>A list of ulong.</returns>
internal static IEnumerable<ulong> GetRoleMentions(DiscordMessage message)
{
var regex = new Regex(@"<@&(\d+)>", RegexOptions.ECMAScript);
var matches = regex.Matches(message.Content);
return from Match match in matches
select ulong.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
}
/// <summary>
/// Gets the channel mentions.
/// </summary>
/// <param name="message">The message.</param>
/// <returns>A list of ulong.</returns>
internal static IEnumerable<ulong> GetChannelMentions(DiscordMessage message)
{
var regex = new Regex(@"<#(\d+)>", RegexOptions.ECMAScript);
var matches = regex.Matches(message.Content);
return from Match match in matches
select ulong.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
}
/// <summary>
/// Gets the emojis.
/// </summary>
/// <param name="message">The message.</param>
/// <returns>A list of ulong.</returns>
internal static IEnumerable<ulong> GetEmojis(DiscordMessage message)
{
var regex = new Regex(@"<a?:([a-zA-Z0-9_]+):(\d+)>", RegexOptions.ECMAScript);
var matches = regex.Matches(message.Content);
return from Match match in matches
select ulong.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture);
}
/// <summary>
/// Are the valid slash command name.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>A bool.</returns>
internal static bool IsValidSlashCommandName(string name)
{
var regex = new Regex(@"^[\w-]{1,32}$");
return regex.IsMatch(name);
}
/// <summary>
/// Checks the thread auto archive duration feature.
/// </summary>
/// <param name="guild">The guild.</param>
/// <param name="taad">The taad.</param>
/// <returns>A bool.</returns>
internal static bool CheckThreadAutoArchiveDurationFeature(DiscordGuild guild, ThreadAutoArchiveDuration taad)
=> true;
/// <summary>
/// Checks the thread private feature.
/// </summary>
/// <param name="guild">The guild.</param>
/// <returns>A bool.</returns>
#pragma warning disable CS0612 // Type or member is obsolete
internal static bool CheckThreadPrivateFeature(DiscordGuild guild) => guild.PremiumTier.HasFlag(PremiumTier.TierTwo) || guild.Features.HasFeature(GuildFeaturesEnum.CanCreatePrivateThreads);
#pragma warning restore CS0612 // Type or member is obsolete
/// <summary>
/// Have the message intents.
/// </summary>
/// <param name="intents">The intents.</param>
/// <returns>A bool.</returns>
internal static bool HasMessageIntents(DiscordIntents intents)
=> intents.HasIntent(DiscordIntents.GuildMessages) || intents.HasIntent(DiscordIntents.DirectMessages);
/// <summary>
/// Have the message intents.
/// </summary>
/// <param name="intents">The intents.</param>
/// <returns>A bool.</returns>
internal static bool HasMessageContentIntents(DiscordIntents intents)
=> intents.HasIntent(DiscordIntents.MessageContent);
/// <summary>
/// Have the reaction intents.
/// </summary>
/// <param name="intents">The intents.</param>
/// <returns>A bool.</returns>
internal static bool HasReactionIntents(DiscordIntents intents)
=> intents.HasIntent(DiscordIntents.GuildMessageReactions) || intents.HasIntent(DiscordIntents.DirectMessageReactions);
/// <summary>
/// Have the typing intents.
/// </summary>
/// <param name="intents">The intents.</param>
/// <returns>A bool.</returns>
internal static bool HasTypingIntents(DiscordIntents intents)
=> intents.HasIntent(DiscordIntents.GuildMessageTyping) || intents.HasIntent(DiscordIntents.DirectMessageTyping);
// https://discord.com/developers/docs/topics/gateway#sharding-sharding-formula
/// <summary>
/// Gets a shard id from a guild id and total shard count.
/// </summary>
/// <param name="guildId">The guild id the shard is on.</param>
/// <param name="shardCount">The total amount of shards.</param>
/// <returns>The shard id.</returns>
public static int GetShardId(ulong guildId, int shardCount)
=> (int)(guildId >> 22) % shardCount;
/// <summary>
/// Helper method to create a <see cref="DateTimeOffset"/> from Unix time seconds for targets that do not support this natively.
/// </summary>
/// <param name="unixTime">Unix time seconds to convert.</param>
/// <param name="shouldThrow">Whether the method should throw on failure. Defaults to true.</param>
/// <returns>Calculated <see cref="DateTimeOffset"/>.</returns>
public static DateTimeOffset GetDateTimeOffset(long unixTime, bool shouldThrow = true)
{
try
{
return DateTimeOffset.FromUnixTimeSeconds(unixTime);
}
catch (Exception)
{
if (shouldThrow)
throw;
return DateTimeOffset.MinValue;
}
}
/// <summary>
/// Helper method to create a <see cref="DateTimeOffset"/> from Unix time milliseconds for targets that do not support this natively.
/// </summary>
/// <param name="unixTime">Unix time milliseconds to convert.</param>
/// <param name="shouldThrow">Whether the method should throw on failure. Defaults to true.</param>
/// <returns>Calculated <see cref="DateTimeOffset"/>.</returns>
public static DateTimeOffset GetDateTimeOffsetFromMilliseconds(long unixTime, bool shouldThrow = true)
{
try
{
return DateTimeOffset.FromUnixTimeMilliseconds(unixTime);
}
catch (Exception)
{
if (shouldThrow)
throw;
return DateTimeOffset.MinValue;
}
}
/// <summary>
/// Helper method to calculate Unix time seconds from a <see cref="DateTimeOffset"/> for targets that do not support this natively.
/// </summary>
/// <param name="dto"><see cref="DateTimeOffset"/> to calculate Unix time for.</param>
/// <returns>Calculated Unix time.</returns>
public static long GetUnixTime(DateTimeOffset dto)
=> dto.ToUnixTimeMilliseconds();
/// <summary>
/// Computes a timestamp from a given snowflake.
/// </summary>
/// <param name="snowflake">Snowflake to compute a timestamp from.</param>
/// <returns>Computed timestamp.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static DateTimeOffset GetSnowflakeTime(this ulong snowflake)
=> DiscordClient.DiscordEpoch.AddMilliseconds(snowflake >> 22);
/// <summary>
/// Computes a timestamp from a given snowflake.
/// </summary>
/// <param name="snowflake">Snowflake to compute a timestamp from.</param>
/// <returns>Computed timestamp.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static DateTimeOffset? GetSnowflakeTime(this ulong? snowflake)
=> snowflake != null && snowflake.HasValue ? DiscordClient.DiscordEpoch.AddMilliseconds(snowflake.Value >> 22) : null;
/// <summary>
/// Converts this <see cref="Permissions"/> into human-readable format.
/// </summary>
/// <param name="perm">Permissions enumeration to convert.</param>
/// <returns>Human-readable permissions.</returns>
public static string ToPermissionString(this Permissions perm)
{
if (perm == Permissions.None)
return PermissionStrings[perm];
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));
}
/// <summary>
/// Checks whether this string contains given characters.
/// </summary>
/// <param name="str">String to check.</param>
/// <param name="characters">Characters to check for.</param>
/// <returns>Whether the string contained these characters.</returns>
public static bool Contains(this string str, params char[] characters)
{
foreach (var xc in str)
if (characters.Contains(xc))
return true;
return false;
}
/// <summary>
/// Logs the task fault.
/// </summary>
/// <param name="task">The task.</param>
/// <param name="logger">The logger.</param>
/// <param name="level">The level.</param>
/// <param name="eventId">The event id.</param>
/// <param name="message">The message.</param>
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);
}
/// <summary>
/// Deconstructs the.
/// </summary>
/// <param name="kvp">The kvp.</param>
/// <param name="key">The key.</param>
/// <param name="value">The value.</param>
internal static void Deconstruct<TKey, TValue>(this KeyValuePair<TKey, TValue> kvp, out TKey key, out TValue value)
{
key = kvp.Key;
value = kvp.Value;
}
}
diff --git a/LICENSE.md b/LICENSE.md
index 980420ad1..9d835003d 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,346 +1,346 @@
The MIT License (MIT)
-Copyright (c) 2021-2022 Aiko IT Systems
+Copyright (c) 2021-2023 Aiko IT Systems
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.
# Package licenses for DisCatSharp
Parts of this package are not provided under DisCatSharp's license. Said
licenses are listed here.
## Package base
[Original license reading](https://github.com/DSharpPlus/DSharpPlus/blob/master/LICENSE)
```
The MIT License (MIT)
Copyright (c) 2015 Mike Santiago
Copyright (c) 2016-2021 DSharpPlus Development Team
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.
```
## Opus
[Original license reading](https://github.com/xiph/opus/blob/master/COPYING)
```
Copyright 2001-2011 Xiph.Org, Skype Limited, Octasic,
Jean-Marc Valin, Timothy B. Terriberry,
CSIRO, Gregory Maxwell, Mark Borgerding,
Erik de Castro Lopo
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of Internet Society, IETF or IETF Trust, nor the
names of specific contributors, may be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Opus is subject to the royalty-free patent licenses which are
specified at:
Xiph.Org Foundation:
https://datatracker.ietf.org/ipr/1524/
Microsoft Corporation:
https://datatracker.ietf.org/ipr/1914/
Broadcom Corporation:
https://datatracker.ietf.org/ipr/1526/
```
## Libsodium
[Original license reading](https://github.com/jedisct1/libsodium/blob/master/LICENSE)
```
/*
* ISC License
*
* Copyright (c) 2013-2020
* Frank Denis <j at pureftpd dot org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
```
## DisCatSharp.Common
```
-Copyright 2021 Aiko IT Systems
+Copyright 2021-2023 Aiko IT Systems
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```
## DisCatSharp.Common Source
[Original license reading](https://github.com/Emzi0767/Common/blob/master/LICENSE.TXT)
```
Copyright 2020-2021 Emzi0767
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
```

File Metadata

Mime Type
application/octet-stream
Expires
Wed, May 8, 00:21 (1 d, 23 h)
Storage Engine
chunks
Storage Format
Chunks
Storage Handle
KtI1shMy5gZF
Default Alt Text
(4 MB)

Event Timeline