diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 5a1d48082..2fe5abfe8 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -14,25 +14,20 @@ trigger:
jobs:
- job: Linux
pool:
- vmImage: 'ubuntu-16.04'
+ vmImage: 'ubuntu-latest'
steps:
- - task: UseDotNet@2
- displayName: 'Use .NET Core sdk'
- inputs:
- packageType: 'sdk'
- version: '3.x'
- template: azure/build.yml
- job: Windows_build
pool:
- vmImage: 'windows-2019'
+ vmImage: 'windows-latest'
condition: ne(variables['Build.SourceBranch'], 'refs/heads/dev')
steps:
- template: azure/build.yml
- job: Windows_deploy
pool:
- vmImage: 'windows-2019'
+ vmImage: 'windows-latest'
condition: |
and (
succeeded(),
diff --git a/azure/build.yml b/azure/build.yml
index 412e4a823..63ba93964 100644
--- a/azure/build.yml
+++ b/azure/build.yml
@@ -1,5 +1,10 @@
steps:
-- script: dotnet restore -v minimal Discord.Net.sln
+- task: DotNetCoreCLI@2
+ inputs:
+ command: 'restore'
+ projects: 'Discord.Net.sln'
+ feedsToUse: 'select'
+ verbosityRestore: 'Minimal'
displayName: Restore packages
- script: dotnet build "Discord.Net.sln" --no-restore -v minimal -c $(buildConfiguration) /p:BuildNumber=$(buildNumber) /p:IsTagBuild=$(buildTag)
diff --git a/samples/01_basic_ping_bot/01_basic_ping_bot.csproj b/samples/01_basic_ping_bot/01_basic_ping_bot.csproj
index 4b4e35e3f..128082edb 100644
--- a/samples/01_basic_ping_bot/01_basic_ping_bot.csproj
+++ b/samples/01_basic_ping_bot/01_basic_ping_bot.csproj
@@ -2,7 +2,7 @@
Exe
- netcoreapp3.0
+ netcoreapp3.1
diff --git a/samples/02_commands_framework/02_commands_framework.csproj b/samples/02_commands_framework/02_commands_framework.csproj
index 84b30aa99..151e546a2 100644
--- a/samples/02_commands_framework/02_commands_framework.csproj
+++ b/samples/02_commands_framework/02_commands_framework.csproj
@@ -2,11 +2,11 @@
Exe
- netcoreapp3.0
+ netcoreapp3.1
-
+
diff --git a/samples/03_sharded_client/03_sharded_client.csproj b/samples/03_sharded_client/03_sharded_client.csproj
index a6599c117..24f9942f9 100644
--- a/samples/03_sharded_client/03_sharded_client.csproj
+++ b/samples/03_sharded_client/03_sharded_client.csproj
@@ -2,12 +2,12 @@
Exe
- netcoreapp3.0
+ netcoreapp3.1
_03_sharded_client
-
+
diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs
index aec8dcbe3..28037b0fa 100644
--- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs
+++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs
@@ -135,7 +135,8 @@ namespace Discord.Commands
if (builder.Name == null)
builder.Name = typeInfo.Name;
- var validCommands = typeInfo.DeclaredMethods.Where(IsValidCommandDefinition);
+ // Get all methods (including from inherited members), that are valid commands
+ var validCommands = typeInfo.GetMethods().Where(IsValidCommandDefinition);
foreach (var method in validCommands)
{
diff --git a/src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs
index 9552b0a60..93ca2e59a 100644
--- a/src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs
+++ b/src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs
@@ -1,3 +1,5 @@
+using System.Collections.Generic;
+
namespace Discord
{
///
@@ -30,5 +32,9 @@ namespace Discord
/// is set.
///
public Optional CategoryId { get; set; }
+ ///
+ /// Gets or sets the permission overwrites for this channel.
+ ///
+ public Optional> PermissionOverwrites { get; set; }
}
}
diff --git a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs
index f5b986295..030a278bc 100644
--- a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs
+++ b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs
@@ -59,11 +59,15 @@ namespace Discord
/// The to be sent.
/// The options to be used when sending the request.
/// Whether the message attachment should be hidden as a spoiler.
+ ///
+ /// Specifies if notifications are sent for mentioned users and roles in the message .
+ /// If null, all mentioned roles and users will be notified.
+ ///
///
/// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message.
///
- Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false);
+ Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null);
///
/// Sends a file to this message channel with an optional caption.
///
@@ -88,11 +92,15 @@ namespace Discord
/// The to be sent.
/// The options to be used when sending the request.
/// Whether the message attachment should be hidden as a spoiler.
+ ///
+ /// Specifies if notifications are sent for mentioned users and roles in the message .
+ /// If null, all mentioned roles and users will be notified.
+ ///
///
/// A task that represents an asynchronous send operation for delivering the message. The task result
/// contains the sent message.
///
- Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false);
+ Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, bool isSpoiler = false, AllowedMentions allowedMentions = null);
///
/// Gets a message from this message channel.
diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
index b39a49776..81b5e8dd9 100644
--- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
+++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
@@ -683,6 +683,9 @@ namespace Discord
///
/// Downloads all users for this guild if the current list is incomplete.
///
+ ///
+ /// This method downloads all users found within this guild throught the Gateway and caches them.
+ ///
///
/// A task that represents the asynchronous download operation.
///
@@ -707,6 +710,22 @@ namespace Discord
/// be or has been removed from this guild.
///
Task PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null);
+ ///
+ /// Gets a collection of users in this guild that the name or nickname starts with the
+ /// provided at .
+ ///
+ ///
+ /// The can not be higher than .
+ ///
+ /// The partial name or nickname to search.
+ /// The maximum number of users to be gotten.
+ /// The that determines whether the object should be fetched from cache.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous get operation. The task result contains a collection of guild
+ /// users that the name or nickname starts with the provided at .
+ ///
+ Task> SearchUsersAsync(string query, int limit = DiscordConfig.MaxUsersPerBatch, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);
///
/// Gets the specified number of audit log entries for this guild.
diff --git a/src/Discord.Net.Core/Entities/Messages/AllowedMentionTypes.cs b/src/Discord.Net.Core/Entities/Messages/AllowedMentionTypes.cs
index 3ce6531b7..ecd872d83 100644
--- a/src/Discord.Net.Core/Entities/Messages/AllowedMentionTypes.cs
+++ b/src/Discord.Net.Core/Entities/Messages/AllowedMentionTypes.cs
@@ -8,17 +8,27 @@ namespace Discord
[Flags]
public enum AllowedMentionTypes
{
+ ///
+ /// No flag is set.
+ ///
+ ///
+ /// This flag is not used to control mentions.
+ ///
+ /// It will always be present and does not mean mentions will not be allowed.
+ ///
+ ///
+ None = 0,
///
/// Controls role mentions.
///
- Roles,
+ Roles = 1,
///
/// Controls user mentions.
///
- Users,
+ Users = 2,
///
/// Controls @everyone
and @here
mentions.
///
- Everyone,
+ Everyone = 4,
}
}
diff --git a/src/Discord.Net.Core/Entities/Messages/AllowedMentions.cs b/src/Discord.Net.Core/Entities/Messages/AllowedMentions.cs
index 9b168bbd0..d52feaa7d 100644
--- a/src/Discord.Net.Core/Entities/Messages/AllowedMentions.cs
+++ b/src/Discord.Net.Core/Entities/Messages/AllowedMentions.cs
@@ -39,7 +39,7 @@ namespace Discord
/// flag of the property. If the flag is set, the value of this property
/// must be null or empty.
///
- public List RoleIds { get; set; }
+ public List RoleIds { get; set; } = new List();
///
/// Gets or sets the list of all user ids that will be mentioned.
@@ -47,7 +47,7 @@ namespace Discord
/// flag of the property. If the flag is set, the value of this property
/// must be null or empty.
///
- public List UserIds { get; set; }
+ public List UserIds { get; set; } = new List();
///
/// Initializes a new instance of the class.
diff --git a/src/Discord.Net.Core/Entities/Messages/IMessage.cs b/src/Discord.Net.Core/Entities/Messages/IMessage.cs
index aac526831..530c1cd82 100644
--- a/src/Discord.Net.Core/Entities/Messages/IMessage.cs
+++ b/src/Discord.Net.Core/Entities/Messages/IMessage.cs
@@ -215,6 +215,15 @@ namespace Discord
/// A task that represents the asynchronous removal operation.
///
Task RemoveAllReactionsAsync(RequestOptions options = null);
+ ///
+ /// Removes all reactions with a specific emoji from this message.
+ ///
+ /// The emoji used to react to this message.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous removal operation.
+ ///
+ Task RemoveAllReactionsForEmoteAsync(IEmote emote, RequestOptions options = null);
///
/// Gets all users that reacted to a message with a given emote.
diff --git a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs
index bc52dd01c..e2fb25aae 100644
--- a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs
+++ b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs
@@ -57,6 +57,21 @@ namespace Discord
///
Task UnpinAsync(RequestOptions options = null);
+ ///
+ /// Publishes (crossposts) this message.
+ ///
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous operation for publishing this message.
+ ///
+ ///
+ ///
+ /// This call will throw an if attempted in a non-news channel.
+ ///
+ /// This method will publish (crosspost) the message. Please note, publishing (crossposting), is only available in news channels.
+ ///
+ Task CrosspostAsync(RequestOptions options = null);
+
///
/// Transforms this message's text into a human-readable form by resolving its tags.
///
diff --git a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs
index 60fa06cbd..92b146e05 100644
--- a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs
+++ b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs
@@ -73,7 +73,7 @@ namespace Discord
///
///
/// The following example checks if the current user has the ability to send a message with attachment in
- /// this channel; if so, uploads a file via .
+ /// this channel; if so, uploads a file via .
///
/// if (currentUser?.GetPermissions(targetChannel)?.AttachFiles)
/// await targetChannel.SendFileAsync("fortnite.png");
diff --git a/src/Discord.Net.Core/Entities/Users/IPresence.cs b/src/Discord.Net.Core/Entities/Users/IPresence.cs
index 620eb907c..a17ac0df2 100644
--- a/src/Discord.Net.Core/Entities/Users/IPresence.cs
+++ b/src/Discord.Net.Core/Entities/Users/IPresence.cs
@@ -19,5 +19,9 @@ namespace Discord
/// Gets the set of clients where this user is currently active.
///
IImmutableSet ActiveClients { get; }
+ ///
+ /// Gets the list of activities that this user currently has available.
+ ///
+ IImmutableList Activities { get; }
}
}
diff --git a/src/Discord.Net.Core/GatewayIntents.cs b/src/Discord.Net.Core/GatewayIntents.cs
new file mode 100644
index 000000000..f3dc5ceb9
--- /dev/null
+++ b/src/Discord.Net.Core/GatewayIntents.cs
@@ -0,0 +1,43 @@
+using System;
+
+namespace Discord
+{
+ [Flags]
+ public enum GatewayIntents
+ {
+ /// This intent includes no events
+ None = 0,
+ /// This intent includes GUILD_CREATE, GUILD_UPDATE, GUILD_DELETE, GUILD_ROLE_CREATE, GUILD_ROLE_UPDATE, GUILD_ROLE_DELETE, CHANNEL_CREATE, CHANNEL_UPDATE, CHANNEL_DELETE, CHANNEL_PINS_UPDATE
+ Guilds = 1 << 0,
+ /// This intent includes GUILD_MEMBER_ADD, GUILD_MEMBER_UPDATE, GUILD_MEMBER_REMOVE
+ /// This is a privileged intent and must be enabled in the Developer Portal.
+ GuildMembers = 1 << 1,
+ /// This intent includes GUILD_BAN_ADD, GUILD_BAN_REMOVE
+ GuildBans = 1 << 2,
+ /// This intent includes GUILD_EMOJIS_UPDATE
+ GuildEmojis = 1 << 3,
+ /// This intent includes GUILD_INTEGRATIONS_UPDATE
+ GuildIntegrations = 1 << 4,
+ /// This intent includes WEBHOOKS_UPDATE
+ GuildWebhooks = 1 << 5,
+ /// This intent includes INVITE_CREATE, INVITE_DELETE
+ GuildInvites = 1 << 6,
+ /// This intent includes VOICE_STATE_UPDATE
+ GuildVoiceStates = 1 << 7,
+ /// This intent includes PRESENCE_UPDATE
+ /// This is a privileged intent and must be enabled in the Developer Portal.
+ GuildPresences = 1 << 8,
+ /// This intent includes MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, MESSAGE_DELETE_BULK
+ GuildMessages = 1 << 9,
+ /// This intent includes MESSAGE_REACTION_ADD, MESSAGE_REACTION_REMOVE, MESSAGE_REACTION_REMOVE_ALL, MESSAGE_REACTION_REMOVE_EMOJI
+ GuildMessageReactions = 1 << 10,
+ /// This intent includes TYPING_START
+ GuildMessageTyping = 1 << 11,
+ /// This intent includes CHANNEL_CREATE, MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, CHANNEL_PINS_UPDATE
+ DirectMessages = 1 << 12,
+ /// This intent includes MESSAGE_REACTION_ADD, MESSAGE_REACTION_REMOVE, MESSAGE_REACTION_REMOVE_ALL, MESSAGE_REACTION_REMOVE_EMOJI
+ DirectMessageReactions = 1 << 13,
+ /// This intent includes TYPING_START
+ DirectMessageTyping = 1 << 14,
+ }
+}
diff --git a/src/Discord.Net.Core/Net/BucketId.cs b/src/Discord.Net.Core/Net/BucketId.cs
new file mode 100644
index 000000000..96281a0ed
--- /dev/null
+++ b/src/Discord.Net.Core/Net/BucketId.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+
+namespace Discord.Net
+{
+ ///
+ /// Represents a ratelimit bucket.
+ ///
+ public class BucketId : IEquatable
+ {
+ ///
+ /// Gets the http method used to make the request if available.
+ ///
+ public string HttpMethod { get; }
+ ///
+ /// Gets the endpoint that is going to be requested if available.
+ ///
+ public string Endpoint { get; }
+ ///
+ /// Gets the major parameters of the route.
+ ///
+ public IOrderedEnumerable> MajorParameters { get; }
+ ///
+ /// Gets the hash of this bucket.
+ ///
+ ///
+ /// The hash is provided by Discord to group ratelimits.
+ ///
+ public string BucketHash { get; }
+ ///
+ /// Gets if this bucket is a hash type.
+ ///
+ public bool IsHashBucket { get => BucketHash != null; }
+
+ private BucketId(string httpMethod, string endpoint, IEnumerable> majorParameters, string bucketHash)
+ {
+ HttpMethod = httpMethod;
+ Endpoint = endpoint;
+ MajorParameters = majorParameters.OrderBy(x => x.Key);
+ BucketHash = bucketHash;
+ }
+
+ ///
+ /// Creates a new based on the
+ /// and .
+ ///
+ /// Http method used to make the request.
+ /// Endpoint that is going to receive requests.
+ /// Major parameters of the route of this endpoint.
+ ///
+ /// A based on the
+ /// and the with the provided data.
+ ///
+ public static BucketId Create(string httpMethod, string endpoint, Dictionary majorParams)
+ {
+ Preconditions.NotNullOrWhitespace(endpoint, nameof(endpoint));
+ majorParams ??= new Dictionary();
+ return new BucketId(httpMethod, endpoint, majorParams, null);
+ }
+
+ ///
+ /// Creates a new based on a
+ /// and a previous .
+ ///
+ /// Bucket hash provided by Discord.
+ /// that is going to be upgraded to a hash type.
+ ///
+ /// A based on the
+ /// and .
+ ///
+ public static BucketId Create(string hash, BucketId oldBucket)
+ {
+ Preconditions.NotNullOrWhitespace(hash, nameof(hash));
+ Preconditions.NotNull(oldBucket, nameof(oldBucket));
+ return new BucketId(null, null, oldBucket.MajorParameters, hash);
+ }
+
+ ///
+ /// Gets the string that will define this bucket as a hash based one.
+ ///
+ ///
+ /// A that defines this bucket as a hash based one.
+ ///
+ public string GetBucketHash()
+ => IsHashBucket ? $"{BucketHash}:{string.Join("/", MajorParameters.Select(x => x.Value))}" : null;
+
+ ///
+ /// Gets the string that will define this bucket as an endpoint based one.
+ ///
+ ///
+ /// A that defines this bucket as an endpoint based one.
+ ///
+ public string GetUniqueEndpoint()
+ => HttpMethod != null ? $"{HttpMethod} {Endpoint}" : Endpoint;
+
+ public override bool Equals(object obj)
+ => Equals(obj as BucketId);
+
+ public override int GetHashCode()
+ => IsHashBucket ? (BucketHash, string.Join("/", MajorParameters.Select(x => x.Value))).GetHashCode() : (HttpMethod, Endpoint).GetHashCode();
+
+ public override string ToString()
+ => GetBucketHash() ?? GetUniqueEndpoint();
+
+ public bool Equals(BucketId other)
+ {
+ if (other is null)
+ return false;
+ if (ReferenceEquals(this, other))
+ return true;
+ if (GetType() != other.GetType())
+ return false;
+ return ToString() == other.ToString();
+ }
+ }
+}
diff --git a/src/Discord.Net.Core/RequestOptions.cs b/src/Discord.Net.Core/RequestOptions.cs
index 02161e64d..dbb240273 100644
--- a/src/Discord.Net.Core/RequestOptions.cs
+++ b/src/Discord.Net.Core/RequestOptions.cs
@@ -1,3 +1,4 @@
+using Discord.Net;
using System.Threading;
namespace Discord
@@ -57,7 +58,7 @@ namespace Discord
public bool? UseSystemClock { get; set; }
internal bool IgnoreState { get; set; }
- internal string BucketId { get; set; }
+ internal BucketId BucketId { get; set; }
internal bool IsClientBucket { get; set; }
internal bool IsReactionBucket { get; set; }
internal bool IsGatewayBucket { get; set; }
diff --git a/src/Discord.Net.Core/Utils/Comparers.cs b/src/Discord.Net.Core/Utils/Comparers.cs
index 7ec9f5c74..3c7b8aa3c 100644
--- a/src/Discord.Net.Core/Utils/Comparers.cs
+++ b/src/Discord.Net.Core/Utils/Comparers.cs
@@ -41,16 +41,13 @@ namespace Discord
{
public override bool Equals(TEntity x, TEntity y)
{
- bool xNull = x == null;
- bool yNull = y == null;
-
- if (xNull && yNull)
- return true;
-
- if (xNull ^ yNull)
- return false;
-
- return x.Id.Equals(y.Id);
+ return (x, y) switch
+ {
+ (null, null) => true,
+ (null, _) => false,
+ (_, null) => false,
+ var (l, r) => l.Id.Equals(r.Id)
+ };
}
public override int GetHashCode(TEntity obj)
diff --git a/src/Discord.Net.Rest/API/Common/Presence.cs b/src/Discord.Net.Rest/API/Common/Presence.cs
index 22526e8ac..b37ad4229 100644
--- a/src/Discord.Net.Rest/API/Common/Presence.cs
+++ b/src/Discord.Net.Rest/API/Common/Presence.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
using Newtonsoft.Json;
+using System;
using System.Collections.Generic;
namespace Discord.API
@@ -26,5 +27,9 @@ namespace Discord.API
// "client_status": { "desktop": "dnd", "mobile": "dnd" }
[JsonProperty("client_status")]
public Optional> ClientStatus { get; set; }
+ [JsonProperty("activities")]
+ public List Activities { get; set; }
+ [JsonProperty("premium_since")]
+ public Optional PremiumSince { get; set; }
}
}
diff --git a/src/Discord.Net.Rest/API/Rest/CreateGuildChannelParams.cs b/src/Discord.Net.Rest/API/Rest/CreateGuildChannelParams.cs
index a102bd38d..aec43dbef 100644
--- a/src/Discord.Net.Rest/API/Rest/CreateGuildChannelParams.cs
+++ b/src/Discord.Net.Rest/API/Rest/CreateGuildChannelParams.cs
@@ -14,12 +14,16 @@ namespace Discord.API.Rest
public Optional CategoryId { get; set; }
[JsonProperty("position")]
public Optional Position { get; set; }
+ [JsonProperty("permission_overwrites")]
+ public Optional Overwrites { get; set; }
//Text channels
[JsonProperty("topic")]
public Optional Topic { get; set; }
[JsonProperty("nsfw")]
public Optional IsNsfw { get; set; }
+ [JsonProperty("rate_limit_per_user")]
+ public Optional SlowModeInterval { get; set; }
//Voice channels
[JsonProperty("bitrate")]
diff --git a/src/Discord.Net.Rest/API/Rest/SearchGuildMembersParams.cs b/src/Discord.Net.Rest/API/Rest/SearchGuildMembersParams.cs
new file mode 100644
index 000000000..7c933ff82
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Rest/SearchGuildMembersParams.cs
@@ -0,0 +1,9 @@
+#pragma warning disable CS1591
+namespace Discord.API.Rest
+{
+ internal class SearchGuildMembersParams
+ {
+ public string Query { get; set; }
+ public Optional Limit { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs b/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs
index 7ba21d012..64535e6d7 100644
--- a/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs
+++ b/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs
@@ -19,6 +19,7 @@ namespace Discord.API.Rest
public Optional Nonce { get; set; }
public Optional IsTTS { get; set; }
public Optional