@@ -7,12 +7,12 @@ namespace Discord | |||
{ | |||
/// <summary> Gets the type of this system message. </summary> | |||
MessageType Type { get; } | |||
/// <summary> Gets the source of this message. </summary> | |||
MessageSource Source { get; } | |||
/// <summary> Returns true if this message was sent as a text-to-speech message. </summary> | |||
bool IsTTS { get; } | |||
/// <summary> Returns true if this message was added to its channel's pinned messages. </summary> | |||
bool IsPinned { get; } | |||
/// <summary> Returns true if this message was created using a webhook. </summary> | |||
bool IsWebhook { get; } | |||
/// <summary> Returns the content for this message. </summary> | |||
string Content { get; } | |||
/// <summary> Gets the time this message was sent. </summary> | |||
@@ -24,8 +24,6 @@ namespace Discord | |||
IMessageChannel Channel { get; } | |||
/// <summary> Gets the author of this message. </summary> | |||
IUser Author { get; } | |||
/// <summary> Gets the id of the webhook used to created this message, if any. </summary> | |||
ulong? WebhookId { get; } | |||
/// <summary> Returns all attachments included in this message. </summary> | |||
IReadOnlyCollection<IAttachment> Attachments { get; } | |||
@@ -0,0 +1,10 @@ | |||
namespace Discord | |||
{ | |||
public enum MessageSource | |||
{ | |||
System, | |||
User, | |||
Bot, | |||
Webhook | |||
} | |||
} |
@@ -7,22 +7,24 @@ namespace Discord | |||
[DebuggerDisplay("{DebuggerDisplay,nq}")] | |||
public struct ChannelPermissions | |||
{ | |||
//TODO: C#7 Candidate for binary literals | |||
private static ChannelPermissions _allDM { get; } = new ChannelPermissions(Convert.ToUInt64("00000000000001011100110000000000", 2)); | |||
private static ChannelPermissions _allVoice { get; } = new ChannelPermissions(Convert.ToUInt64("00010011111100000000000000010001", 2)); | |||
private static ChannelPermissions _allText { get; } = new ChannelPermissions(Convert.ToUInt64("00010000000001111111110001010001", 2)); | |||
private static ChannelPermissions _allGroup { get; } = new ChannelPermissions(Convert.ToUInt64("00000000000001111110110000000000", 2)); | |||
/// <summary> Gets a blank ChannelPermissions that grants no permissions. </summary> | |||
public static ChannelPermissions None { get; } = new ChannelPermissions(); | |||
public static readonly ChannelPermissions None = new ChannelPermissions(); | |||
/// <summary> Gets a ChannelPermissions that grants all permissions for text channels. </summary> | |||
public static readonly ChannelPermissions Text = new ChannelPermissions(0b00100_0000000_1111111110001_010001); | |||
/// <summary> Gets a ChannelPermissions that grants all permissions for voice channels. </summary> | |||
public static readonly ChannelPermissions Voice = new ChannelPermissions(0b00100_1111110_0000000000000_010001); | |||
/// <summary> Gets a ChannelPermissions that grants all permissions for direct message channels. </summary> | |||
public static readonly ChannelPermissions DM = new ChannelPermissions(0b00000_1000110_1011100110000_000000); | |||
/// <summary> Gets a ChannelPermissions that grants all permissions for group channels. </summary> | |||
public static readonly ChannelPermissions Group = new ChannelPermissions(0b00000_1000110_0001101100000_000000); | |||
/// <summary> Gets a ChannelPermissions that grants all permissions for a given channelType. </summary> | |||
public static ChannelPermissions All(IChannel channel) | |||
{ | |||
//TODO: C#7 Candidate for typeswitch | |||
if (channel is ITextChannel) return _allText; | |||
if (channel is IVoiceChannel) return _allVoice; | |||
if (channel is IDMChannel) return _allDM; | |||
if (channel is IGroupChannel) return _allGroup; | |||
if (channel is ITextChannel) return Text; | |||
if (channel is IVoiceChannel) return Voice; | |||
if (channel is IDMChannel) return DM; | |||
if (channel is IGroupChannel) return Group; | |||
throw new ArgumentException("Unknown channel type", nameof(channel)); | |||
} | |||
@@ -77,7 +79,7 @@ namespace Discord | |||
/// <summary> Creates a new ChannelPermissions with the provided packed value. </summary> | |||
public ChannelPermissions(ulong rawValue) { RawValue = rawValue; } | |||
private ChannelPermissions(ulong initialValue, bool? createInstantInvite = null, bool? manageChannel = null, | |||
private ChannelPermissions(ulong initialValue, bool? createInstantInvite = null, bool? manageChannel = null, | |||
bool? addReactions = null, | |||
bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, | |||
bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, | |||
@@ -111,25 +113,26 @@ namespace Discord | |||
} | |||
/// <summary> Creates a new ChannelPermissions with the provided permissions. </summary> | |||
public ChannelPermissions(bool createInstantInvite = false, bool manageChannel = false, | |||
public ChannelPermissions(bool createInstantInvite = false, bool manageChannel = false, | |||
bool addReactions = false, | |||
bool readMessages = false, bool sendMessages = false, bool sendTTSMessages = false, bool manageMessages = false, | |||
bool embedLinks = false, bool attachFiles = false, bool readMessageHistory = false, bool mentionEveryone = false, | |||
bool useExternalEmojis = false, bool connect = false, bool speak = false, bool muteMembers = false, bool deafenMembers = false, | |||
bool moveMembers = false, bool useVoiceActivation = false, bool managePermissions = false, bool manageWebhooks = false) | |||
: this(0, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages, | |||
embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, | |||
speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, managePermissions, manageWebhooks) { } | |||
: this(0, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages, | |||
embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, | |||
speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, managePermissions, manageWebhooks) | |||
{ } | |||
/// <summary> Creates a new ChannelPermissions from this one, changing the provided non-null permissions. </summary> | |||
public ChannelPermissions Modify(bool? createInstantInvite = null, bool? manageChannel = null, | |||
public ChannelPermissions Modify(bool? createInstantInvite = null, bool? manageChannel = null, | |||
bool? addReactions = null, | |||
bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, | |||
bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, | |||
bool useExternalEmojis = false, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, | |||
bool? moveMembers = null, bool? useVoiceActivation = null, bool? managePermissions = null, bool? manageWebhooks = null) | |||
=> new ChannelPermissions(RawValue, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages, | |||
embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, | |||
embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, | |||
speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, managePermissions, manageWebhooks); | |||
public bool Has(ChannelPermission permission) => Permissions.GetValue(RawValue, permission); | |||
@@ -1,5 +1,4 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
namespace Discord | |||
@@ -9,9 +8,10 @@ namespace Discord | |||
{ | |||
/// <summary> Gets a blank GuildPermissions that grants no permissions. </summary> | |||
public static readonly GuildPermissions None = new GuildPermissions(); | |||
/// <summary> Gets a GuildPermissions that grants all permissions. </summary> | |||
//TODO: C#7 Candidate for binary literals | |||
public static readonly GuildPermissions All = new GuildPermissions(Convert.ToUInt64("01111111111100111111110001111111", 2)); | |||
/// <summary> Gets a GuildPermissions that grants all guild permissions for webhook users. </summary> | |||
public static readonly GuildPermissions Webhook = new GuildPermissions(0b00000_0000000_0001101100000_000000); | |||
/// <summary> Gets a GuildPermissions that grants all guild permissions. </summary> | |||
public static readonly GuildPermissions All = new GuildPermissions(0b11111_1111110_0111111110001_111111); | |||
/// <summary> Gets a packed value representing all the permissions in this GuildPermissions. </summary> | |||
public ulong RawValue { get; } | |||
@@ -12,8 +12,10 @@ namespace Discord | |||
string Discriminator { get; } | |||
/// <summary> Gets the per-username unique id for this user. </summary> | |||
ushort DiscriminatorValue { get; } | |||
/// <summary> Returns true if this user is a bot account. </summary> | |||
/// <summary> Returns true if this user is a bot user. </summary> | |||
bool IsBot { get; } | |||
/// <summary> Returns true if this user is a webhook user. </summary> | |||
bool IsWebhook { get; } | |||
/// <summary> Gets the username for this user. </summary> | |||
string Username { get; } | |||
@@ -0,0 +1,8 @@ | |||
namespace Discord | |||
{ | |||
//TODO: Add webhook endpoints | |||
public interface IWebhookUser : IGuildUser | |||
{ | |||
ulong WebhookId { get; } | |||
} | |||
} |
@@ -37,11 +37,8 @@ namespace Discord | |||
public static async Task InvokeAsync(this AsyncEvent<Func<Task>> eventHandler) | |||
{ | |||
var subscribers = eventHandler.Subscriptions; | |||
if (subscribers.Count > 0) | |||
{ | |||
for (int i = 0; i < subscribers.Count; i++) | |||
await subscribers[i].Invoke().ConfigureAwait(false); | |||
} | |||
for (int i = 0; i < subscribers.Count; i++) | |||
await subscribers[i].Invoke().ConfigureAwait(false); | |||
} | |||
public static async Task InvokeAsync<T>(this AsyncEvent<Func<T, Task>> eventHandler, T arg) | |||
{ | |||
@@ -51,5 +51,9 @@ namespace Discord | |||
{ | |||
public static Optional<T> Create<T>() => Optional<T>.Unspecified; | |||
public static Optional<T> Create<T>(T value) => new Optional<T>(value); | |||
public static T? ToNullable<T>(this Optional<T> val) | |||
where T : struct | |||
=> val.IsSpecified ? val.Value : (T?)null; | |||
} | |||
} |
@@ -86,12 +86,16 @@ namespace Discord | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void UnsetBit(ref ulong value, byte bit) => value &= ~(1U << bit); | |||
public static ChannelPermissions ToChannelPerms(IGuildChannel channel, ulong guildPermissions) | |||
=> new ChannelPermissions(guildPermissions & ChannelPermissions.All(channel).RawValue); | |||
public static ulong ResolveGuild(IGuild guild, IGuildUser user) | |||
{ | |||
ulong resolvedPermissions = 0; | |||
if (user.Id == guild.OwnerId) | |||
resolvedPermissions = GuildPermissions.All.RawValue; //Owners always have all permissions | |||
else if (user.IsWebhook) | |||
resolvedPermissions = GuildPermissions.Webhook.RawValue; | |||
else | |||
{ | |||
foreach (var roleId in user.RoleIds) | |||
@@ -91,7 +91,7 @@ namespace Discord.Rest | |||
var guildId = (channel as IGuildChannel)?.GuildId; | |||
var guild = guildId != null ? await (client as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).ConfigureAwait(false) : null; | |||
var model = await client.ApiClient.GetChannelMessageAsync(channel.Id, id, options).ConfigureAwait(false); | |||
var author = GetAuthor(client, guild, model.Author.Value); | |||
var author = GetAuthor(client, guild, model.Author.Value, model.WebhookId.ToNullable()); | |||
return RestMessage.Create(client, channel, author, model); | |||
} | |||
public static IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessageChannel channel, BaseDiscordClient client, | |||
@@ -119,7 +119,7 @@ namespace Discord.Rest | |||
var builder = ImmutableArray.CreateBuilder<RestMessage>(); | |||
foreach (var model in models) | |||
{ | |||
var author = GetAuthor(client, guild, model.Author.Value); | |||
var author = GetAuthor(client, guild, model.Author.Value, model.WebhookId.ToNullable()); | |||
builder.Add(RestMessage.Create(client, channel, author, model)); | |||
} | |||
return builder.ToImmutable(); | |||
@@ -147,7 +147,7 @@ namespace Discord.Rest | |||
var builder = ImmutableArray.CreateBuilder<RestMessage>(); | |||
foreach (var model in models) | |||
{ | |||
var author = GetAuthor(client, guild, model.Author.Value); | |||
var author = GetAuthor(client, guild, model.Author.Value, model.WebhookId.ToNullable()); | |||
builder.Add(RestMessage.Create(client, channel, author, model)); | |||
} | |||
return builder.ToImmutable(); | |||
@@ -264,13 +264,13 @@ namespace Discord.Rest | |||
=> new TypingNotifier(client, channel, options); | |||
//Helpers | |||
private static IUser GetAuthor(BaseDiscordClient client, IGuild guild, UserModel model) | |||
private static IUser GetAuthor(BaseDiscordClient client, IGuild guild, UserModel model, ulong? webhookId) | |||
{ | |||
IUser author = null; | |||
if (guild != null) | |||
author = guild.GetUserAsync(model.Id, CacheMode.CacheOnly).Result; | |||
if (author == null) | |||
author = RestUser.Create(client, model); | |||
author = RestUser.Create(client, guild, model, webhookId); | |||
return author; | |||
} | |||
} | |||
@@ -67,7 +67,7 @@ namespace Discord.Rest | |||
await client.ApiClient.RemovePinAsync(msg.Channel.Id, msg.Id, options).ConfigureAwait(false); | |||
} | |||
public static ImmutableArray<ITag> ParseTags(string text, IMessageChannel channel, IGuild guild, ImmutableArray<IUser> userMentions) | |||
public static ImmutableArray<ITag> ParseTags(string text, IMessageChannel channel, IGuild guild, IReadOnlyCollection<IUser> userMentions) | |||
{ | |||
var tags = ImmutableArray.CreateBuilder<ITag>(); | |||
@@ -156,5 +156,16 @@ namespace Discord.Rest | |||
.Where(x => x != null) | |||
.ToImmutableArray(); | |||
} | |||
public static MessageSource GetSource(Model msg) | |||
{ | |||
if (msg.Type != MessageType.Default) | |||
return MessageSource.System; | |||
else if (msg.WebhookId.IsSpecified) | |||
return MessageSource.Webhook; | |||
else if (msg.Author.GetValueOrDefault()?.Bot.GetValueOrDefault(false) == true) | |||
return MessageSource.Bot; | |||
return MessageSource.User; | |||
} | |||
} | |||
} |
@@ -13,6 +13,7 @@ namespace Discord.Rest | |||
public IMessageChannel Channel { get; } | |||
public IUser Author { get; } | |||
public MessageSource Source { get; } | |||
public string Content { get; private set; } | |||
@@ -26,16 +27,15 @@ namespace Discord.Rest | |||
public virtual IReadOnlyCollection<ulong> MentionedRoleIds => ImmutableArray.Create<ulong>(); | |||
public virtual IReadOnlyCollection<RestUser> MentionedUsers => ImmutableArray.Create<RestUser>(); | |||
public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>(); | |||
public virtual ulong? WebhookId => null; | |||
public bool IsWebhook => WebhookId != null; | |||
public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); | |||
internal RestMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author) | |||
internal RestMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source) | |||
: base(discord, id) | |||
{ | |||
Channel = channel; | |||
Author = author; | |||
Source = source; | |||
} | |||
internal static RestMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model) | |||
{ | |||
@@ -9,7 +9,7 @@ namespace Discord.Rest | |||
public MessageType Type { get; private set; } | |||
internal RestSystemMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author) | |||
: base(discord, id, channel, author) | |||
: base(discord, id, channel, author, MessageSource.System) | |||
{ | |||
} | |||
internal new static RestSystemMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model) | |||
@@ -13,7 +13,6 @@ namespace Discord.Rest | |||
{ | |||
private bool _isMentioningEveryone, _isTTS, _isPinned; | |||
private long? _editedTimestampTicks; | |||
private ulong? _webhookId; | |||
private ImmutableArray<Attachment> _attachments; | |||
private ImmutableArray<Embed> _embeds; | |||
private ImmutableArray<ITag> _tags; | |||
@@ -21,7 +20,6 @@ namespace Discord.Rest | |||
public override bool IsTTS => _isTTS; | |||
public override bool IsPinned => _isPinned; | |||
public override ulong? WebhookId => _webhookId; | |||
public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); | |||
public override IReadOnlyCollection<Attachment> Attachments => _attachments; | |||
public override IReadOnlyCollection<Embed> Embeds => _embeds; | |||
@@ -31,13 +29,13 @@ namespace Discord.Rest | |||
public override IReadOnlyCollection<ITag> Tags => _tags; | |||
public IReadOnlyDictionary<Emoji, ReactionMetadata> Reactions => _reactions.ToDictionary(x => x.Emoji, x => new ReactionMetadata { ReactionCount = x.Count, IsMe = x.Me }); | |||
internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author) | |||
: base(discord, id, channel, author) | |||
internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source) | |||
: base(discord, id, channel, author, source) | |||
{ | |||
} | |||
internal new static RestUserMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model) | |||
internal static new RestUserMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model) | |||
{ | |||
var entity = new RestUserMessage(discord, model.Id, channel, author); | |||
var entity = new RestUserMessage(discord, model.Id, channel, author, MessageHelper.GetSource(model)); | |||
entity.Update(model); | |||
return entity; | |||
} | |||
@@ -54,8 +52,6 @@ namespace Discord.Rest | |||
_editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks; | |||
if (model.MentionEveryone.IsSpecified) | |||
_isMentioningEveryone = model.MentionEveryone.Value; | |||
if (model.WebhookId.IsSpecified) | |||
_webhookId = model.WebhookId.Value; | |||
if (model.Attachments.IsSpecified) | |||
{ | |||
@@ -13,21 +13,26 @@ namespace Discord.Rest | |||
public ushort DiscriminatorValue { get; private set; } | |||
public string AvatarId { get; private set; } | |||
public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) | |||
=> CDN.GetUserAvatarUrl(Id, AvatarId, size, format); | |||
public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); | |||
public string Discriminator => DiscriminatorValue.ToString("D4"); | |||
public string Mention => MentionUtils.MentionUser(Id); | |||
public virtual Game? Game => null; | |||
public virtual UserStatus Status => UserStatus.Offline; | |||
public virtual bool IsWebhook => false; | |||
internal RestUser(BaseDiscordClient discord, ulong id) | |||
: base(discord, id) | |||
{ | |||
} | |||
internal static RestUser Create(BaseDiscordClient discord, Model model) | |||
=> Create(discord, null, model, null); | |||
internal static RestUser Create(BaseDiscordClient discord, IGuild guild, Model model, ulong? webhookId) | |||
{ | |||
var entity = new RestUser(discord, model.Id); | |||
RestUser entity; | |||
if (webhookId.HasValue) | |||
entity = new RestWebhookUser(discord, guild, model.Id, webhookId.Value); | |||
else | |||
entity = new RestUser(discord, model.Id); | |||
entity.Update(model); | |||
return entity; | |||
} | |||
@@ -52,6 +57,9 @@ namespace Discord.Rest | |||
public Task<RestDMChannel> CreateDMChannelAsync(RequestOptions options = null) | |||
=> UserHelper.CreateDMChannelAsync(this, Discord, options); | |||
public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) | |||
=> CDN.GetUserAvatarUrl(Id, AvatarId, size, format); | |||
public override string ToString() => $"{Username}#{Discriminator}"; | |||
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; | |||
@@ -0,0 +1,83 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
using System.Diagnostics; | |||
using System.Threading.Tasks; | |||
using Model = Discord.API.User; | |||
namespace Discord.Rest | |||
{ | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public class RestWebhookUser : RestUser, IWebhookUser | |||
{ | |||
public ulong WebhookId { get; } | |||
internal IGuild Guild { get; } | |||
public override bool IsWebhook => true; | |||
public ulong GuildId => Guild.Id; | |||
internal RestWebhookUser(BaseDiscordClient discord, IGuild guild, ulong id, ulong webhookId) | |||
: base(discord, id) | |||
{ | |||
Guild = guild; | |||
WebhookId = webhookId; | |||
} | |||
internal static RestWebhookUser Create(BaseDiscordClient discord, IGuild guild, Model model, ulong webhookId) | |||
{ | |||
var entity = new RestWebhookUser(discord, guild, model.Id, webhookId); | |||
entity.Update(model); | |||
return entity; | |||
} | |||
//IGuildUser | |||
IGuild IGuildUser.Guild | |||
{ | |||
get | |||
{ | |||
if (Guild != null) | |||
return Guild; | |||
throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); | |||
} | |||
} | |||
IReadOnlyCollection<ulong> IGuildUser.RoleIds => ImmutableArray.Create<ulong>(); | |||
DateTimeOffset? IGuildUser.JoinedAt => null; | |||
string IGuildUser.Nickname => null; | |||
GuildPermissions IGuildUser.GuildPermissions => GuildPermissions.Webhook; | |||
ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) => Permissions.ToChannelPerms(channel, GuildPermissions.Webhook.RawValue); | |||
Task IGuildUser.KickAsync(RequestOptions options) | |||
{ | |||
throw new NotSupportedException("Webhook users cannot be kicked."); | |||
} | |||
Task IGuildUser.ModifyAsync(Action<GuildUserProperties> func, RequestOptions options) | |||
{ | |||
throw new NotSupportedException("Webhook users cannot be modified."); | |||
} | |||
Task IGuildUser.AddRoleAsync(IRole role, RequestOptions options) | |||
{ | |||
throw new NotSupportedException("Roles are not supported on webhook users."); | |||
} | |||
Task IGuildUser.AddRolesAsync(IEnumerable<IRole> roles, RequestOptions options) | |||
{ | |||
throw new NotSupportedException("Roles are not supported on webhook users."); | |||
} | |||
Task IGuildUser.RemoveRoleAsync(IRole role, RequestOptions options) | |||
{ | |||
throw new NotSupportedException("Roles are not supported on webhook users."); | |||
} | |||
Task IGuildUser.RemoveRolesAsync(IEnumerable<IRole> roles, RequestOptions options) | |||
{ | |||
throw new NotSupportedException("Roles are not supported on webhook users."); | |||
} | |||
//IVoiceState | |||
bool IVoiceState.IsDeafened => false; | |||
bool IVoiceState.IsMuted => false; | |||
bool IVoiceState.IsSelfDeafened => false; | |||
bool IVoiceState.IsSelfMuted => false; | |||
bool IVoiceState.IsSuppressed => false; | |||
IVoiceChannel IVoiceState.VoiceChannel => null; | |||
string IVoiceState.VoiceSessionId => null; | |||
} | |||
} |
@@ -13,6 +13,7 @@ namespace Discord.Rpc | |||
public IMessageChannel Channel { get; } | |||
public RpcUser Author { get; } | |||
public MessageSource Source { get; } | |||
public string Content { get; private set; } | |||
public Color AuthorColor { get; private set; } | |||
@@ -33,11 +34,12 @@ namespace Discord.Rpc | |||
public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); | |||
internal RpcMessage(DiscordRpcClient discord, ulong id, RestVirtualMessageChannel channel, RpcUser author) | |||
internal RpcMessage(DiscordRpcClient discord, ulong id, RestVirtualMessageChannel channel, RpcUser author, MessageSource source) | |||
: base(discord, id) | |||
{ | |||
Channel = channel; | |||
Author = author; | |||
Source = source; | |||
} | |||
internal static RpcMessage Create(DiscordRpcClient discord, ulong channelId, Model model) | |||
{ | |||
@@ -10,14 +10,14 @@ namespace Discord.Rpc | |||
public MessageType Type { get; private set; } | |||
internal RpcSystemMessage(DiscordRpcClient discord, ulong id, RestVirtualMessageChannel channel, RpcUser author) | |||
: base(discord, id, channel, author) | |||
: base(discord, id, channel, author, MessageSource.System) | |||
{ | |||
} | |||
internal new static RpcSystemMessage Create(DiscordRpcClient discord, ulong channelId, Model model) | |||
{ | |||
var entity = new RpcSystemMessage(discord, model.Id, | |||
RestVirtualMessageChannel.Create(discord, channelId), | |||
RpcUser.Create(discord, model.Author.Value)); | |||
RpcUser.Create(discord, model.Author.Value, model.WebhookId.ToNullable())); | |||
entity.Update(model); | |||
return entity; | |||
} | |||
@@ -31,15 +31,16 @@ namespace Discord.Rpc | |||
public override IReadOnlyCollection<ITag> Tags => _tags; | |||
public IReadOnlyDictionary<Emoji, ReactionMetadata> Reactions => ImmutableDictionary.Create<Emoji, ReactionMetadata>(); | |||
internal RpcUserMessage(DiscordRpcClient discord, ulong id, RestVirtualMessageChannel channel, RpcUser author) | |||
: base(discord, id, channel, author) | |||
internal RpcUserMessage(DiscordRpcClient discord, ulong id, RestVirtualMessageChannel channel, RpcUser author, MessageSource source) | |||
: base(discord, id, channel, author, source) | |||
{ | |||
} | |||
internal new static RpcUserMessage Create(DiscordRpcClient discord, ulong channelId, Model model) | |||
{ | |||
var entity = new RpcUserMessage(discord, model.Id, | |||
RestVirtualMessageChannel.Create(discord, channelId), | |||
RpcUser.Create(discord, model.Author.Value)); | |||
RpcUser.Create(discord, model.Author.Value, model.WebhookId.ToNullable()), | |||
MessageHelper.GetSource(model)); | |||
entity.Update(model); | |||
return entity; | |||
} | |||
@@ -14,11 +14,10 @@ namespace Discord.Rpc | |||
public ushort DiscriminatorValue { get; private set; } | |||
public string AvatarId { get; private set; } | |||
public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) | |||
=> CDN.GetUserAvatarUrl(Id, AvatarId, size, format); | |||
public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); | |||
public string Discriminator => DiscriminatorValue.ToString("D4"); | |||
public string Mention => MentionUtils.MentionUser(Id); | |||
public virtual bool IsWebhook => false; | |||
public virtual Game? Game => null; | |||
public virtual UserStatus Status => UserStatus.Offline; | |||
@@ -27,8 +26,14 @@ namespace Discord.Rpc | |||
{ | |||
} | |||
internal static RpcUser Create(DiscordRpcClient discord, Model model) | |||
=> Create(discord, model, null); | |||
internal static RpcUser Create(DiscordRpcClient discord, Model model, ulong? webhookId) | |||
{ | |||
var entity = new RpcUser(discord, model.Id); | |||
RpcUser entity; | |||
if (webhookId.HasValue) | |||
entity = new RpcWebhookUser(discord, model.Id, webhookId.Value); | |||
else | |||
entity = new RpcUser(discord, model.Id); | |||
entity.Update(model); | |||
return entity; | |||
} | |||
@@ -47,6 +52,9 @@ namespace Discord.Rpc | |||
public Task<RestDMChannel> CreateDMChannelAsync(RequestOptions options = null) | |||
=> UserHelper.CreateDMChannelAsync(this, Discord, options); | |||
public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) | |||
=> CDN.GetUserAvatarUrl(Id, AvatarId, size, format); | |||
public override string ToString() => $"{Username}#{Discriminator}"; | |||
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; | |||
@@ -0,0 +1,25 @@ | |||
using System.Diagnostics; | |||
using Model = Discord.API.User; | |||
namespace Discord.Rpc | |||
{ | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public class RpcWebhookUser : RpcUser | |||
{ | |||
public ulong WebhookId { get; } | |||
public override bool IsWebhook => true; | |||
internal RpcWebhookUser(DiscordRpcClient discord, ulong id, ulong webhookId) | |||
: base(discord, id) | |||
{ | |||
WebhookId = webhookId; | |||
} | |||
internal static RpcWebhookUser Create(DiscordRpcClient discord, Model model, ulong webhookId) | |||
{ | |||
var entity = new RpcWebhookUser(discord, model.Id, webhookId); | |||
entity.Update(model); | |||
return entity; | |||
} | |||
} | |||
} |
@@ -1045,8 +1045,11 @@ namespace Discord.WebSocket | |||
await _gatewayLogger.DebugAsync("Ignored GUILD_BAN_ADD, guild is not synced yet.").ConfigureAwait(false); | |||
return; | |||
} | |||
await _userBannedEvent.InvokeAsync(SocketSimpleUser.Create(this, State, data.User), guild).ConfigureAwait(false); | |||
SocketUser user = guild.GetUser(data.User.Id); | |||
if (user == null) | |||
user = SocketUnknownUser.Create(this, State, data.User); | |||
await _userBannedEvent.InvokeAsync(user, guild).ConfigureAwait(false); | |||
} | |||
else | |||
{ | |||
@@ -1071,7 +1074,7 @@ namespace Discord.WebSocket | |||
SocketUser user = State.GetUser(data.User.Id); | |||
if (user == null) | |||
user = SocketSimpleUser.Create(this, State, data.User); | |||
user = SocketUnknownUser.Create(this, State, data.User); | |||
await _userUnbannedEvent.InvokeAsync(user, guild).ConfigureAwait(false); | |||
} | |||
else | |||
@@ -1098,8 +1101,16 @@ namespace Discord.WebSocket | |||
return; | |||
} | |||
var author = (guild != null ? guild.GetUser(data.Author.Value.Id) : (channel as SocketChannel).GetUser(data.Author.Value.Id)) ?? | |||
SocketSimpleUser.Create(this, State, data.Author.Value); | |||
SocketUser author; | |||
if (guild != null) | |||
{ | |||
if (data.WebhookId.IsSpecified) | |||
author = SocketWebhookUser.Create(guild, State, data.Author.Value, data.WebhookId.Value); | |||
else | |||
author = guild.GetUser(data.Author.Value.Id); | |||
} | |||
else | |||
author = (channel as SocketChannel).GetUser(data.Author.Value.Id); | |||
if (author != null) | |||
{ | |||
@@ -1153,7 +1164,7 @@ namespace Discord.WebSocket | |||
else | |||
author = (channel as SocketChannel).GetUser(data.Author.Value.Id); | |||
if (author == null) | |||
author = SocketSimpleUser.Create(this, State, data.Author.Value); | |||
author = SocketUnknownUser.Create(this, State, data.Author.Value); | |||
after = SocketMessage.Create(this, State, author, channel, data); | |||
} | |||
@@ -14,6 +14,7 @@ namespace Discord.WebSocket | |||
public SocketUser Author { get; } | |||
public ISocketMessageChannel Channel { get; } | |||
public MessageSource Source { get; } | |||
public string Content { get; private set; } | |||
@@ -27,16 +28,15 @@ namespace Discord.WebSocket | |||
public virtual IReadOnlyCollection<SocketRole> MentionedRoles => ImmutableArray.Create<SocketRole>(); | |||
public virtual IReadOnlyCollection<SocketUser> MentionedUsers => ImmutableArray.Create<SocketUser>(); | |||
public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>(); | |||
public virtual ulong? WebhookId => null; | |||
public bool IsWebhook => WebhookId != null; | |||
public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); | |||
internal SocketMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author) | |||
internal SocketMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author, MessageSource source) | |||
: base(discord, id) | |||
{ | |||
Channel = channel; | |||
Author = author; | |||
Source = source; | |||
} | |||
internal static SocketMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, ISocketMessageChannel channel, Model model) | |||
{ | |||
@@ -9,7 +9,7 @@ namespace Discord.WebSocket | |||
public MessageType Type { get; private set; } | |||
internal SocketSystemMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author) | |||
: base(discord, id, channel, author) | |||
: base(discord, id, channel, author, MessageSource.System) | |||
{ | |||
} | |||
internal new static SocketSystemMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, ISocketMessageChannel channel, Model model) | |||
@@ -14,7 +14,6 @@ namespace Discord.WebSocket | |||
{ | |||
private bool _isMentioningEveryone, _isTTS, _isPinned; | |||
private long? _editedTimestampTicks; | |||
private ulong? _webhookId; | |||
private ImmutableArray<Attachment> _attachments; | |||
private ImmutableArray<Embed> _embeds; | |||
private ImmutableArray<ITag> _tags; | |||
@@ -22,7 +21,6 @@ namespace Discord.WebSocket | |||
public override bool IsTTS => _isTTS; | |||
public override bool IsPinned => _isPinned; | |||
public override ulong? WebhookId => _webhookId; | |||
public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); | |||
public override IReadOnlyCollection<Attachment> Attachments => _attachments; | |||
public override IReadOnlyCollection<Embed> Embeds => _embeds; | |||
@@ -32,13 +30,13 @@ namespace Discord.WebSocket | |||
public override IReadOnlyCollection<SocketUser> MentionedUsers => MessageHelper.FilterTagsByValue<SocketUser>(TagType.UserMention, _tags); | |||
public IReadOnlyDictionary<Emoji, ReactionMetadata> Reactions => _reactions.GroupBy(r => r.Emoji).ToDictionary(x => x.Key, x => new ReactionMetadata { ReactionCount = x.Count(), IsMe = x.Any(y => y.UserId == Discord.CurrentUser.Id) }); | |||
internal SocketUserMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author) | |||
: base(discord, id, channel, author) | |||
internal SocketUserMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author, MessageSource source) | |||
: base(discord, id, channel, author, source) | |||
{ | |||
} | |||
internal new static SocketUserMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, ISocketMessageChannel channel, Model model) | |||
internal static new SocketUserMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, ISocketMessageChannel channel, Model model) | |||
{ | |||
var entity = new SocketUserMessage(discord, model.Id, channel, author); | |||
var entity = new SocketUserMessage(discord, model.Id, channel, author, MessageHelper.GetSource(model)); | |||
entity.Update(state, model); | |||
return entity; | |||
} | |||
@@ -55,8 +53,6 @@ namespace Discord.WebSocket | |||
_editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks; | |||
if (model.MentionEveryone.IsSpecified) | |||
_isMentioningEveryone = model.MentionEveryone.Value; | |||
if (model.WebhookId.IsSpecified) | |||
_webhookId = model.WebhookId.Value; | |||
if (model.Attachments.IsSpecified) | |||
{ | |||
@@ -86,18 +82,18 @@ namespace Discord.WebSocket | |||
_embeds = ImmutableArray.Create<Embed>(); | |||
} | |||
ImmutableArray<IUser> mentions = ImmutableArray.Create<IUser>(); | |||
IReadOnlyCollection<IUser> mentions = ImmutableArray.Create<SocketUnknownUser>(); //Is passed to ParseTags to get real mention collection | |||
if (model.UserMentions.IsSpecified) | |||
{ | |||
var value = model.UserMentions.Value; | |||
if (value.Length > 0) | |||
{ | |||
var newMentions = ImmutableArray.CreateBuilder<IUser>(value.Length); | |||
var newMentions = ImmutableArray.CreateBuilder<SocketUnknownUser>(value.Length); | |||
for (int i = 0; i < value.Length; i++) | |||
{ | |||
var val = value[i]; | |||
if (val.Object != null) | |||
newMentions.Add(SocketSimpleUser.Create(Discord, Discord.State, val.Object)); | |||
newMentions.Add(SocketUnknownUser.Create(Discord, state, val.Object)); | |||
} | |||
mentions = newMentions.ToImmutable(); | |||
} | |||
@@ -11,9 +11,10 @@ namespace Discord.WebSocket | |||
public override ushort DiscriminatorValue { get; internal set; } | |||
public override string AvatarId { get; internal set; } | |||
public SocketDMChannel DMChannel { get; internal set; } | |||
internal override SocketPresence Presence { get; set; } | |||
public override bool IsWebhook => false; | |||
internal override SocketGlobalUser GlobalUser => this; | |||
internal override SocketPresence Presence { get; set; } | |||
private readonly object _lockObj = new object(); | |||
private ushort _references; | |||
@@ -15,6 +15,8 @@ namespace Discord.WebSocket | |||
public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } } | |||
internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } } | |||
public override bool IsWebhook => false; | |||
internal SocketGroupUser(SocketGroupChannel channel, SocketGlobalUser globalUser) | |||
: base(channel.Discord, globalUser.Id) | |||
{ | |||
@@ -28,6 +28,7 @@ namespace Discord.WebSocket | |||
public GuildPermissions GuildPermissions => new GuildPermissions(Permissions.ResolveGuild(Guild, this)); | |||
internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } } | |||
public override bool IsWebhook => false; | |||
public bool IsSelfDeafened => VoiceState?.IsSelfDeafened ?? false; | |||
public bool IsSelfMuted => VoiceState?.IsSelfMuted ?? false; | |||
public bool IsSuppressed => VoiceState?.IsSuppressed ?? false; | |||
@@ -20,6 +20,8 @@ namespace Discord.WebSocket | |||
public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } } | |||
internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } } | |||
public override bool IsWebhook => false; | |||
internal SocketSelfUser(DiscordSocketClient discord, SocketGlobalUser globalUser) | |||
: base(discord, globalUser.Id) | |||
{ | |||
@@ -6,23 +6,25 @@ using PresenceModel = Discord.API.Presence; | |||
namespace Discord.WebSocket | |||
{ | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public class SocketSimpleUser : SocketUser | |||
public class SocketUnknownUser : SocketUser | |||
{ | |||
public override bool IsBot { get; internal set; } | |||
public override string Username { get; internal set; } | |||
public override ushort DiscriminatorValue { get; internal set; } | |||
public override string AvatarId { get; internal set; } | |||
internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null); } set { } } | |||
public override bool IsBot { get; internal set; } | |||
internal override SocketGlobalUser GlobalUser { get { throw new NotSupportedException(); } } | |||
public override bool IsWebhook => false; | |||
internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null); } set { } } | |||
internal override SocketGlobalUser GlobalUser { get { throw new NotSupportedException(); } } | |||
internal SocketSimpleUser(DiscordSocketClient discord, ulong id) | |||
internal SocketUnknownUser(DiscordSocketClient discord, ulong id) | |||
: base(discord, id) | |||
{ | |||
} | |||
internal static SocketSimpleUser Create(DiscordSocketClient discord, ClientState state, Model model) | |||
internal static SocketUnknownUser Create(DiscordSocketClient discord, ClientState state, Model model) | |||
{ | |||
var entity = new SocketSimpleUser(discord, model.Id); | |||
var entity = new SocketUnknownUser(discord, model.Id); | |||
entity.Update(state, model); | |||
return entity; | |||
} | |||
@@ -39,6 +41,6 @@ namespace Discord.WebSocket | |||
Username = model.User.Username.Value; | |||
} | |||
internal new SocketSimpleUser Clone() => MemberwiseClone() as SocketSimpleUser; | |||
internal new SocketUnknownUser Clone() => MemberwiseClone() as SocketUnknownUser; | |||
} | |||
} |
@@ -12,11 +12,10 @@ namespace Discord.WebSocket | |||
public abstract string Username { get; internal set; } | |||
public abstract ushort DiscriminatorValue { get; internal set; } | |||
public abstract string AvatarId { get; internal set; } | |||
public abstract bool IsWebhook { get; } | |||
internal abstract SocketGlobalUser GlobalUser { get; } | |||
internal abstract SocketPresence Presence { get; set; } | |||
public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) | |||
=> CDN.GetUserAvatarUrl(Id, AvatarId, size, format); | |||
public DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); | |||
public string Discriminator => DiscriminatorValue.ToString("D4"); | |||
public string Mention => MentionUtils.MentionUser(Id); | |||
@@ -47,6 +46,9 @@ namespace Discord.WebSocket | |||
public Task<RestDMChannel> CreateDMChannelAsync(RequestOptions options = null) | |||
=> UserHelper.CreateDMChannelAsync(this, Discord, options); | |||
public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) | |||
=> CDN.GetUserAvatarUrl(Id, AvatarId, size, format); | |||
public override string ToString() => $"{Username}#{Discriminator}"; | |||
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; | |||
internal SocketUser Clone() => MemberwiseClone() as SocketUser; | |||
@@ -0,0 +1,98 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
using System.Diagnostics; | |||
using System.Threading.Tasks; | |||
using Model = Discord.API.User; | |||
using PresenceModel = Discord.API.Presence; | |||
namespace Discord.WebSocket | |||
{ | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public class SocketWebhookUser : SocketUser, IWebhookUser | |||
{ | |||
public SocketGuild Guild { get; } | |||
public ulong WebhookId { get; } | |||
public override string Username { get; internal set; } | |||
public override ushort DiscriminatorValue { get; internal set; } | |||
public override string AvatarId { get; internal set; } | |||
public override bool IsBot { get; internal set; } | |||
public override bool IsWebhook => true; | |||
internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null); } set { } } | |||
internal override SocketGlobalUser GlobalUser { get { throw new NotSupportedException(); } } | |||
internal SocketWebhookUser(SocketGuild guild, ulong id, ulong webhookId) | |||
: base(guild.Discord, id) | |||
{ | |||
WebhookId = webhookId; | |||
} | |||
internal static SocketWebhookUser Create(SocketGuild guild, ClientState state, Model model, ulong webhookId) | |||
{ | |||
var entity = new SocketWebhookUser(guild, model.Id, webhookId); | |||
entity.Update(state, model); | |||
return entity; | |||
} | |||
internal override void Update(ClientState state, PresenceModel model) | |||
{ | |||
if (model.User.Avatar.IsSpecified) | |||
AvatarId = model.User.Avatar.Value; | |||
if (model.User.Discriminator.IsSpecified) | |||
DiscriminatorValue = ushort.Parse(model.User.Discriminator.Value); | |||
if (model.User.Bot.IsSpecified) | |||
IsBot = model.User.Bot.Value; | |||
if (model.User.Username.IsSpecified) | |||
Username = model.User.Username.Value; | |||
} | |||
internal new SocketWebhookUser Clone() => MemberwiseClone() as SocketWebhookUser; | |||
//IGuildUser | |||
IGuild IGuildUser.Guild => Guild; | |||
ulong IGuildUser.GuildId => Guild.Id; | |||
IReadOnlyCollection<ulong> IGuildUser.RoleIds => ImmutableArray.Create<ulong>(); | |||
DateTimeOffset? IGuildUser.JoinedAt => null; | |||
string IGuildUser.Nickname => null; | |||
GuildPermissions IGuildUser.GuildPermissions => GuildPermissions.Webhook; | |||
ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) => Permissions.ToChannelPerms(channel, GuildPermissions.Webhook.RawValue); | |||
Task IGuildUser.KickAsync(RequestOptions options) | |||
{ | |||
throw new NotSupportedException("Webhook users cannot be kicked."); | |||
} | |||
Task IGuildUser.ModifyAsync(Action<GuildUserProperties> func, RequestOptions options) | |||
{ | |||
throw new NotSupportedException("Webhook users cannot be modified."); | |||
} | |||
Task IGuildUser.AddRoleAsync(IRole role, RequestOptions options) | |||
{ | |||
throw new NotSupportedException("Roles are not supported on webhook users."); | |||
} | |||
Task IGuildUser.AddRolesAsync(IEnumerable<IRole> roles, RequestOptions options) | |||
{ | |||
throw new NotSupportedException("Roles are not supported on webhook users."); | |||
} | |||
Task IGuildUser.RemoveRoleAsync(IRole role, RequestOptions options) | |||
{ | |||
throw new NotSupportedException("Roles are not supported on webhook users."); | |||
} | |||
Task IGuildUser.RemoveRolesAsync(IEnumerable<IRole> roles, RequestOptions options) | |||
{ | |||
throw new NotSupportedException("Roles are not supported on webhook users."); | |||
} | |||
//IVoiceState | |||
bool IVoiceState.IsDeafened => false; | |||
bool IVoiceState.IsMuted => false; | |||
bool IVoiceState.IsSelfDeafened => false; | |||
bool IVoiceState.IsSelfMuted => false; | |||
bool IVoiceState.IsSuppressed => false; | |||
IVoiceChannel IVoiceState.VoiceChannel => null; | |||
string IVoiceState.VoiceSessionId => null; | |||
} | |||
} |