| @@ -7,12 +7,12 @@ namespace Discord | |||||
| { | { | ||||
| /// <summary> Gets the type of this system message. </summary> | /// <summary> Gets the type of this system message. </summary> | ||||
| MessageType Type { get; } | 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> | /// <summary> Returns true if this message was sent as a text-to-speech message. </summary> | ||||
| bool IsTTS { get; } | bool IsTTS { get; } | ||||
| /// <summary> Returns true if this message was added to its channel's pinned messages. </summary> | /// <summary> Returns true if this message was added to its channel's pinned messages. </summary> | ||||
| bool IsPinned { get; } | 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> | /// <summary> Returns the content for this message. </summary> | ||||
| string Content { get; } | string Content { get; } | ||||
| /// <summary> Gets the time this message was sent. </summary> | /// <summary> Gets the time this message was sent. </summary> | ||||
| @@ -24,8 +24,6 @@ namespace Discord | |||||
| IMessageChannel Channel { get; } | IMessageChannel Channel { get; } | ||||
| /// <summary> Gets the author of this message. </summary> | /// <summary> Gets the author of this message. </summary> | ||||
| IUser Author { get; } | 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> | /// <summary> Returns all attachments included in this message. </summary> | ||||
| IReadOnlyCollection<IAttachment> Attachments { get; } | 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}")] | [DebuggerDisplay("{DebuggerDisplay,nq}")] | ||||
| public struct ChannelPermissions | 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> | /// <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> | /// <summary> Gets a ChannelPermissions that grants all permissions for a given channelType. </summary> | ||||
| public static ChannelPermissions All(IChannel channel) | public static ChannelPermissions All(IChannel channel) | ||||
| { | { | ||||
| //TODO: C#7 Candidate for typeswitch | //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)); | 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> | /// <summary> Creates a new ChannelPermissions with the provided packed value. </summary> | ||||
| public ChannelPermissions(ulong rawValue) { RawValue = rawValue; } | 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? addReactions = null, | ||||
| bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = 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? 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> | /// <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 addReactions = false, | ||||
| bool readMessages = false, bool sendMessages = false, bool sendTTSMessages = false, bool manageMessages = 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 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 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) | 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> | /// <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? addReactions = null, | ||||
| bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = 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? 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 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) | bool? moveMembers = null, bool? useVoiceActivation = null, bool? managePermissions = null, bool? manageWebhooks = null) | ||||
| => new ChannelPermissions(RawValue, createInstantInvite, manageChannel, addReactions, readMessages, sendMessages, sendTTSMessages, manageMessages, | => 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); | speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, managePermissions, manageWebhooks); | ||||
| public bool Has(ChannelPermission permission) => Permissions.GetValue(RawValue, permission); | 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; | using System.Diagnostics; | ||||
| namespace Discord | namespace Discord | ||||
| @@ -9,9 +8,10 @@ namespace Discord | |||||
| { | { | ||||
| /// <summary> Gets a blank GuildPermissions that grants no permissions. </summary> | /// <summary> Gets a blank GuildPermissions that grants no permissions. </summary> | ||||
| public static readonly GuildPermissions None = new GuildPermissions(); | 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> | /// <summary> Gets a packed value representing all the permissions in this GuildPermissions. </summary> | ||||
| public ulong RawValue { get; } | public ulong RawValue { get; } | ||||
| @@ -12,8 +12,10 @@ namespace Discord | |||||
| string Discriminator { get; } | string Discriminator { get; } | ||||
| /// <summary> Gets the per-username unique id for this user. </summary> | /// <summary> Gets the per-username unique id for this user. </summary> | ||||
| ushort DiscriminatorValue { get; } | 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; } | 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> | /// <summary> Gets the username for this user. </summary> | ||||
| string Username { get; } | 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) | public static async Task InvokeAsync(this AsyncEvent<Func<Task>> eventHandler) | ||||
| { | { | ||||
| var subscribers = eventHandler.Subscriptions; | 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) | 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>() => Optional<T>.Unspecified; | ||||
| public static Optional<T> Create<T>(T value) => new Optional<T>(value); | 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)] | [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||
| public static void UnsetBit(ref ulong value, byte bit) => value &= ~(1U << bit); | 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) | public static ulong ResolveGuild(IGuild guild, IGuildUser user) | ||||
| { | { | ||||
| ulong resolvedPermissions = 0; | ulong resolvedPermissions = 0; | ||||
| if (user.Id == guild.OwnerId) | if (user.Id == guild.OwnerId) | ||||
| resolvedPermissions = GuildPermissions.All.RawValue; //Owners always have all permissions | resolvedPermissions = GuildPermissions.All.RawValue; //Owners always have all permissions | ||||
| else if (user.IsWebhook) | |||||
| resolvedPermissions = GuildPermissions.Webhook.RawValue; | |||||
| else | else | ||||
| { | { | ||||
| foreach (var roleId in user.RoleIds) | foreach (var roleId in user.RoleIds) | ||||
| @@ -91,7 +91,7 @@ namespace Discord.Rest | |||||
| var guildId = (channel as IGuildChannel)?.GuildId; | var guildId = (channel as IGuildChannel)?.GuildId; | ||||
| var guild = guildId != null ? await (client as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).ConfigureAwait(false) : null; | 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 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); | return RestMessage.Create(client, channel, author, model); | ||||
| } | } | ||||
| public static IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessageChannel channel, BaseDiscordClient client, | public static IAsyncEnumerable<IReadOnlyCollection<RestMessage>> GetMessagesAsync(IMessageChannel channel, BaseDiscordClient client, | ||||
| @@ -119,7 +119,7 @@ namespace Discord.Rest | |||||
| var builder = ImmutableArray.CreateBuilder<RestMessage>(); | var builder = ImmutableArray.CreateBuilder<RestMessage>(); | ||||
| foreach (var model in models) | 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)); | builder.Add(RestMessage.Create(client, channel, author, model)); | ||||
| } | } | ||||
| return builder.ToImmutable(); | return builder.ToImmutable(); | ||||
| @@ -147,7 +147,7 @@ namespace Discord.Rest | |||||
| var builder = ImmutableArray.CreateBuilder<RestMessage>(); | var builder = ImmutableArray.CreateBuilder<RestMessage>(); | ||||
| foreach (var model in models) | 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)); | builder.Add(RestMessage.Create(client, channel, author, model)); | ||||
| } | } | ||||
| return builder.ToImmutable(); | return builder.ToImmutable(); | ||||
| @@ -264,13 +264,13 @@ namespace Discord.Rest | |||||
| => new TypingNotifier(client, channel, options); | => new TypingNotifier(client, channel, options); | ||||
| //Helpers | //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; | IUser author = null; | ||||
| if (guild != null) | if (guild != null) | ||||
| author = guild.GetUserAsync(model.Id, CacheMode.CacheOnly).Result; | author = guild.GetUserAsync(model.Id, CacheMode.CacheOnly).Result; | ||||
| if (author == null) | if (author == null) | ||||
| author = RestUser.Create(client, model); | |||||
| author = RestUser.Create(client, guild, model, webhookId); | |||||
| return author; | return author; | ||||
| } | } | ||||
| } | } | ||||
| @@ -67,7 +67,7 @@ namespace Discord.Rest | |||||
| await client.ApiClient.RemovePinAsync(msg.Channel.Id, msg.Id, options).ConfigureAwait(false); | 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>(); | var tags = ImmutableArray.CreateBuilder<ITag>(); | ||||
| @@ -156,5 +156,16 @@ namespace Discord.Rest | |||||
| .Where(x => x != null) | .Where(x => x != null) | ||||
| .ToImmutableArray(); | .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 IMessageChannel Channel { get; } | ||||
| public IUser Author { get; } | public IUser Author { get; } | ||||
| public MessageSource Source { get; } | |||||
| public string Content { get; private set; } | public string Content { get; private set; } | ||||
| @@ -26,16 +27,15 @@ namespace Discord.Rest | |||||
| public virtual IReadOnlyCollection<ulong> MentionedRoleIds => ImmutableArray.Create<ulong>(); | public virtual IReadOnlyCollection<ulong> MentionedRoleIds => ImmutableArray.Create<ulong>(); | ||||
| public virtual IReadOnlyCollection<RestUser> MentionedUsers => ImmutableArray.Create<RestUser>(); | public virtual IReadOnlyCollection<RestUser> MentionedUsers => ImmutableArray.Create<RestUser>(); | ||||
| public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>(); | public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>(); | ||||
| public virtual ulong? WebhookId => null; | |||||
| public bool IsWebhook => WebhookId != null; | |||||
| public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); | 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) | : base(discord, id) | ||||
| { | { | ||||
| Channel = channel; | Channel = channel; | ||||
| Author = author; | Author = author; | ||||
| Source = source; | |||||
| } | } | ||||
| internal static RestMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model) | 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; } | public MessageType Type { get; private set; } | ||||
| internal RestSystemMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author) | 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) | 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 bool _isMentioningEveryone, _isTTS, _isPinned; | ||||
| private long? _editedTimestampTicks; | private long? _editedTimestampTicks; | ||||
| private ulong? _webhookId; | |||||
| private ImmutableArray<Attachment> _attachments; | private ImmutableArray<Attachment> _attachments; | ||||
| private ImmutableArray<Embed> _embeds; | private ImmutableArray<Embed> _embeds; | ||||
| private ImmutableArray<ITag> _tags; | private ImmutableArray<ITag> _tags; | ||||
| @@ -21,7 +20,6 @@ namespace Discord.Rest | |||||
| public override bool IsTTS => _isTTS; | public override bool IsTTS => _isTTS; | ||||
| public override bool IsPinned => _isPinned; | public override bool IsPinned => _isPinned; | ||||
| public override ulong? WebhookId => _webhookId; | |||||
| public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); | public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); | ||||
| public override IReadOnlyCollection<Attachment> Attachments => _attachments; | public override IReadOnlyCollection<Attachment> Attachments => _attachments; | ||||
| public override IReadOnlyCollection<Embed> Embeds => _embeds; | public override IReadOnlyCollection<Embed> Embeds => _embeds; | ||||
| @@ -31,13 +29,13 @@ namespace Discord.Rest | |||||
| public override IReadOnlyCollection<ITag> Tags => _tags; | 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 }); | 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); | entity.Update(model); | ||||
| return entity; | return entity; | ||||
| } | } | ||||
| @@ -54,8 +52,6 @@ namespace Discord.Rest | |||||
| _editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks; | _editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks; | ||||
| if (model.MentionEveryone.IsSpecified) | if (model.MentionEveryone.IsSpecified) | ||||
| _isMentioningEveryone = model.MentionEveryone.Value; | _isMentioningEveryone = model.MentionEveryone.Value; | ||||
| if (model.WebhookId.IsSpecified) | |||||
| _webhookId = model.WebhookId.Value; | |||||
| if (model.Attachments.IsSpecified) | if (model.Attachments.IsSpecified) | ||||
| { | { | ||||
| @@ -13,21 +13,26 @@ namespace Discord.Rest | |||||
| public ushort DiscriminatorValue { get; private set; } | public ushort DiscriminatorValue { get; private set; } | ||||
| public string AvatarId { 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 DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); | ||||
| public string Discriminator => DiscriminatorValue.ToString("D4"); | public string Discriminator => DiscriminatorValue.ToString("D4"); | ||||
| public string Mention => MentionUtils.MentionUser(Id); | public string Mention => MentionUtils.MentionUser(Id); | ||||
| public virtual Game? Game => null; | public virtual Game? Game => null; | ||||
| public virtual UserStatus Status => UserStatus.Offline; | public virtual UserStatus Status => UserStatus.Offline; | ||||
| public virtual bool IsWebhook => false; | |||||
| internal RestUser(BaseDiscordClient discord, ulong id) | internal RestUser(BaseDiscordClient discord, ulong id) | ||||
| : base(discord, id) | : base(discord, id) | ||||
| { | { | ||||
| } | } | ||||
| internal static RestUser Create(BaseDiscordClient discord, Model model) | 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); | entity.Update(model); | ||||
| return entity; | return entity; | ||||
| } | } | ||||
| @@ -52,6 +57,9 @@ namespace Discord.Rest | |||||
| public Task<RestDMChannel> CreateDMChannelAsync(RequestOptions options = null) | public Task<RestDMChannel> CreateDMChannelAsync(RequestOptions options = null) | ||||
| => UserHelper.CreateDMChannelAsync(this, Discord, options); | => 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}"; | public override string ToString() => $"{Username}#{Discriminator}"; | ||||
| private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; | 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 IMessageChannel Channel { get; } | ||||
| public RpcUser Author { get; } | public RpcUser Author { get; } | ||||
| public MessageSource Source { get; } | |||||
| public string Content { get; private set; } | public string Content { get; private set; } | ||||
| public Color AuthorColor { get; private set; } | public Color AuthorColor { get; private set; } | ||||
| @@ -33,11 +34,12 @@ namespace Discord.Rpc | |||||
| public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); | 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) | : base(discord, id) | ||||
| { | { | ||||
| Channel = channel; | Channel = channel; | ||||
| Author = author; | Author = author; | ||||
| Source = source; | |||||
| } | } | ||||
| internal static RpcMessage Create(DiscordRpcClient discord, ulong channelId, Model model) | internal static RpcMessage Create(DiscordRpcClient discord, ulong channelId, Model model) | ||||
| { | { | ||||
| @@ -10,14 +10,14 @@ namespace Discord.Rpc | |||||
| public MessageType Type { get; private set; } | public MessageType Type { get; private set; } | ||||
| internal RpcSystemMessage(DiscordRpcClient discord, ulong id, RestVirtualMessageChannel channel, RpcUser author) | 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) | internal new static RpcSystemMessage Create(DiscordRpcClient discord, ulong channelId, Model model) | ||||
| { | { | ||||
| var entity = new RpcSystemMessage(discord, model.Id, | var entity = new RpcSystemMessage(discord, model.Id, | ||||
| RestVirtualMessageChannel.Create(discord, channelId), | RestVirtualMessageChannel.Create(discord, channelId), | ||||
| RpcUser.Create(discord, model.Author.Value)); | |||||
| RpcUser.Create(discord, model.Author.Value, model.WebhookId.ToNullable())); | |||||
| entity.Update(model); | entity.Update(model); | ||||
| return entity; | return entity; | ||||
| } | } | ||||
| @@ -31,15 +31,16 @@ namespace Discord.Rpc | |||||
| public override IReadOnlyCollection<ITag> Tags => _tags; | public override IReadOnlyCollection<ITag> Tags => _tags; | ||||
| public IReadOnlyDictionary<Emoji, ReactionMetadata> Reactions => ImmutableDictionary.Create<Emoji, ReactionMetadata>(); | 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) | internal new static RpcUserMessage Create(DiscordRpcClient discord, ulong channelId, Model model) | ||||
| { | { | ||||
| var entity = new RpcUserMessage(discord, model.Id, | var entity = new RpcUserMessage(discord, model.Id, | ||||
| RestVirtualMessageChannel.Create(discord, channelId), | 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); | entity.Update(model); | ||||
| return entity; | return entity; | ||||
| } | } | ||||
| @@ -14,11 +14,10 @@ namespace Discord.Rpc | |||||
| public ushort DiscriminatorValue { get; private set; } | public ushort DiscriminatorValue { get; private set; } | ||||
| public string AvatarId { 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 DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); | ||||
| public string Discriminator => DiscriminatorValue.ToString("D4"); | public string Discriminator => DiscriminatorValue.ToString("D4"); | ||||
| public string Mention => MentionUtils.MentionUser(Id); | public string Mention => MentionUtils.MentionUser(Id); | ||||
| public virtual bool IsWebhook => false; | |||||
| public virtual Game? Game => null; | public virtual Game? Game => null; | ||||
| public virtual UserStatus Status => UserStatus.Offline; | public virtual UserStatus Status => UserStatus.Offline; | ||||
| @@ -27,8 +26,14 @@ namespace Discord.Rpc | |||||
| { | { | ||||
| } | } | ||||
| internal static RpcUser Create(DiscordRpcClient discord, Model model) | 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); | entity.Update(model); | ||||
| return entity; | return entity; | ||||
| } | } | ||||
| @@ -47,6 +52,9 @@ namespace Discord.Rpc | |||||
| public Task<RestDMChannel> CreateDMChannelAsync(RequestOptions options = null) | public Task<RestDMChannel> CreateDMChannelAsync(RequestOptions options = null) | ||||
| => UserHelper.CreateDMChannelAsync(this, Discord, options); | => 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}"; | public override string ToString() => $"{Username}#{Discriminator}"; | ||||
| private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; | 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); | await _gatewayLogger.DebugAsync("Ignored GUILD_BAN_ADD, guild is not synced yet.").ConfigureAwait(false); | ||||
| return; | 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 | else | ||||
| { | { | ||||
| @@ -1071,7 +1074,7 @@ namespace Discord.WebSocket | |||||
| SocketUser user = State.GetUser(data.User.Id); | SocketUser user = State.GetUser(data.User.Id); | ||||
| if (user == null) | if (user == null) | ||||
| user = SocketSimpleUser.Create(this, State, data.User); | |||||
| user = SocketUnknownUser.Create(this, State, data.User); | |||||
| await _userUnbannedEvent.InvokeAsync(user, guild).ConfigureAwait(false); | await _userUnbannedEvent.InvokeAsync(user, guild).ConfigureAwait(false); | ||||
| } | } | ||||
| else | else | ||||
| @@ -1098,8 +1101,16 @@ namespace Discord.WebSocket | |||||
| return; | 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) | if (author != null) | ||||
| { | { | ||||
| @@ -1153,7 +1164,7 @@ namespace Discord.WebSocket | |||||
| else | else | ||||
| author = (channel as SocketChannel).GetUser(data.Author.Value.Id); | author = (channel as SocketChannel).GetUser(data.Author.Value.Id); | ||||
| if (author == null) | 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); | after = SocketMessage.Create(this, State, author, channel, data); | ||||
| } | } | ||||
| @@ -14,6 +14,7 @@ namespace Discord.WebSocket | |||||
| public SocketUser Author { get; } | public SocketUser Author { get; } | ||||
| public ISocketMessageChannel Channel { get; } | public ISocketMessageChannel Channel { get; } | ||||
| public MessageSource Source { get; } | |||||
| public string Content { get; private set; } | public string Content { get; private set; } | ||||
| @@ -27,16 +28,15 @@ namespace Discord.WebSocket | |||||
| public virtual IReadOnlyCollection<SocketRole> MentionedRoles => ImmutableArray.Create<SocketRole>(); | public virtual IReadOnlyCollection<SocketRole> MentionedRoles => ImmutableArray.Create<SocketRole>(); | ||||
| public virtual IReadOnlyCollection<SocketUser> MentionedUsers => ImmutableArray.Create<SocketUser>(); | public virtual IReadOnlyCollection<SocketUser> MentionedUsers => ImmutableArray.Create<SocketUser>(); | ||||
| public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>(); | public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>(); | ||||
| public virtual ulong? WebhookId => null; | |||||
| public bool IsWebhook => WebhookId != null; | |||||
| public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); | 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) | : base(discord, id) | ||||
| { | { | ||||
| Channel = channel; | Channel = channel; | ||||
| Author = author; | Author = author; | ||||
| Source = source; | |||||
| } | } | ||||
| internal static SocketMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, ISocketMessageChannel channel, Model model) | 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; } | public MessageType Type { get; private set; } | ||||
| internal SocketSystemMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author) | 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) | 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 bool _isMentioningEveryone, _isTTS, _isPinned; | ||||
| private long? _editedTimestampTicks; | private long? _editedTimestampTicks; | ||||
| private ulong? _webhookId; | |||||
| private ImmutableArray<Attachment> _attachments; | private ImmutableArray<Attachment> _attachments; | ||||
| private ImmutableArray<Embed> _embeds; | private ImmutableArray<Embed> _embeds; | ||||
| private ImmutableArray<ITag> _tags; | private ImmutableArray<ITag> _tags; | ||||
| @@ -22,7 +21,6 @@ namespace Discord.WebSocket | |||||
| public override bool IsTTS => _isTTS; | public override bool IsTTS => _isTTS; | ||||
| public override bool IsPinned => _isPinned; | public override bool IsPinned => _isPinned; | ||||
| public override ulong? WebhookId => _webhookId; | |||||
| public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); | public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); | ||||
| public override IReadOnlyCollection<Attachment> Attachments => _attachments; | public override IReadOnlyCollection<Attachment> Attachments => _attachments; | ||||
| public override IReadOnlyCollection<Embed> Embeds => _embeds; | 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 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) }); | 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); | entity.Update(state, model); | ||||
| return entity; | return entity; | ||||
| } | } | ||||
| @@ -55,8 +53,6 @@ namespace Discord.WebSocket | |||||
| _editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks; | _editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks; | ||||
| if (model.MentionEveryone.IsSpecified) | if (model.MentionEveryone.IsSpecified) | ||||
| _isMentioningEveryone = model.MentionEveryone.Value; | _isMentioningEveryone = model.MentionEveryone.Value; | ||||
| if (model.WebhookId.IsSpecified) | |||||
| _webhookId = model.WebhookId.Value; | |||||
| if (model.Attachments.IsSpecified) | if (model.Attachments.IsSpecified) | ||||
| { | { | ||||
| @@ -86,18 +82,18 @@ namespace Discord.WebSocket | |||||
| _embeds = ImmutableArray.Create<Embed>(); | _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) | if (model.UserMentions.IsSpecified) | ||||
| { | { | ||||
| var value = model.UserMentions.Value; | var value = model.UserMentions.Value; | ||||
| if (value.Length > 0) | 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++) | for (int i = 0; i < value.Length; i++) | ||||
| { | { | ||||
| var val = value[i]; | var val = value[i]; | ||||
| if (val.Object != null) | if (val.Object != null) | ||||
| newMentions.Add(SocketSimpleUser.Create(Discord, Discord.State, val.Object)); | |||||
| newMentions.Add(SocketUnknownUser.Create(Discord, state, val.Object)); | |||||
| } | } | ||||
| mentions = newMentions.ToImmutable(); | mentions = newMentions.ToImmutable(); | ||||
| } | } | ||||
| @@ -11,9 +11,10 @@ namespace Discord.WebSocket | |||||
| public override ushort DiscriminatorValue { get; internal set; } | public override ushort DiscriminatorValue { get; internal set; } | ||||
| public override string AvatarId { get; internal set; } | public override string AvatarId { get; internal set; } | ||||
| public SocketDMChannel DMChannel { 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 SocketGlobalUser GlobalUser => this; | ||||
| internal override SocketPresence Presence { get; set; } | |||||
| private readonly object _lockObj = new object(); | private readonly object _lockObj = new object(); | ||||
| private ushort _references; | private ushort _references; | ||||
| @@ -15,6 +15,8 @@ namespace Discord.WebSocket | |||||
| public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } } | 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; } } | internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } } | ||||
| public override bool IsWebhook => false; | |||||
| internal SocketGroupUser(SocketGroupChannel channel, SocketGlobalUser globalUser) | internal SocketGroupUser(SocketGroupChannel channel, SocketGlobalUser globalUser) | ||||
| : base(channel.Discord, globalUser.Id) | : base(channel.Discord, globalUser.Id) | ||||
| { | { | ||||
| @@ -28,6 +28,7 @@ namespace Discord.WebSocket | |||||
| public GuildPermissions GuildPermissions => new GuildPermissions(Permissions.ResolveGuild(Guild, this)); | public GuildPermissions GuildPermissions => new GuildPermissions(Permissions.ResolveGuild(Guild, this)); | ||||
| internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } } | 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 IsSelfDeafened => VoiceState?.IsSelfDeafened ?? false; | ||||
| public bool IsSelfMuted => VoiceState?.IsSelfMuted ?? false; | public bool IsSelfMuted => VoiceState?.IsSelfMuted ?? false; | ||||
| public bool IsSuppressed => VoiceState?.IsSuppressed ?? 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; } } | 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; } } | internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } } | ||||
| public override bool IsWebhook => false; | |||||
| internal SocketSelfUser(DiscordSocketClient discord, SocketGlobalUser globalUser) | internal SocketSelfUser(DiscordSocketClient discord, SocketGlobalUser globalUser) | ||||
| : base(discord, globalUser.Id) | : base(discord, globalUser.Id) | ||||
| { | { | ||||
| @@ -6,23 +6,25 @@ using PresenceModel = Discord.API.Presence; | |||||
| namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
| { | { | ||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [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 string Username { get; internal set; } | ||||
| public override ushort DiscriminatorValue { get; internal set; } | public override ushort DiscriminatorValue { get; internal set; } | ||||
| public override string AvatarId { 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) | : 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); | entity.Update(state, model); | ||||
| return entity; | return entity; | ||||
| } | } | ||||
| @@ -39,6 +41,6 @@ namespace Discord.WebSocket | |||||
| Username = model.User.Username.Value; | 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 string Username { get; internal set; } | ||||
| public abstract ushort DiscriminatorValue { get; internal set; } | public abstract ushort DiscriminatorValue { get; internal set; } | ||||
| public abstract string AvatarId { get; internal set; } | public abstract string AvatarId { get; internal set; } | ||||
| public abstract bool IsWebhook { get; } | |||||
| internal abstract SocketGlobalUser GlobalUser { get; } | internal abstract SocketGlobalUser GlobalUser { get; } | ||||
| internal abstract SocketPresence Presence { get; set; } | 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 DateTimeOffset CreatedAt => DateTimeUtils.FromSnowflake(Id); | ||||
| public string Discriminator => DiscriminatorValue.ToString("D4"); | public string Discriminator => DiscriminatorValue.ToString("D4"); | ||||
| public string Mention => MentionUtils.MentionUser(Id); | public string Mention => MentionUtils.MentionUser(Id); | ||||
| @@ -47,6 +46,9 @@ namespace Discord.WebSocket | |||||
| public Task<RestDMChannel> CreateDMChannelAsync(RequestOptions options = null) | public Task<RestDMChannel> CreateDMChannelAsync(RequestOptions options = null) | ||||
| => UserHelper.CreateDMChannelAsync(this, Discord, options); | => 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}"; | public override string ToString() => $"{Username}#{Discriminator}"; | ||||
| private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; | private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; | ||||
| internal SocketUser Clone() => MemberwiseClone() as SocketUser; | 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; | |||||
| } | |||||
| } | |||||