| @@ -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; | |||
| } | |||
| } | |||