Browse Source

more work on cache provider

v4/state-cache-providers
Quin Lynch 3 years ago
parent
commit
67c5094462
45 changed files with 1066 additions and 259 deletions
  1. +12
    -7
      src/Discord.Net.Core/Cache/CacheableEntityExtensions.cs
  2. +16
    -0
      src/Discord.Net.Core/Cache/Models/Application/IPartialApplicationModel.cs
  3. +35
    -0
      src/Discord.Net.Core/Cache/Models/Message/Components/IMessageComponentModel.cs
  4. +22
    -0
      src/Discord.Net.Core/Cache/Models/Message/Components/IMessageComponentOptionModel.cs
  5. +21
    -0
      src/Discord.Net.Core/Cache/Models/Message/IAttachmentModel.cs
  6. +44
    -0
      src/Discord.Net.Core/Cache/Models/Message/IEmbedModel.cs
  7. +14
    -0
      src/Discord.Net.Core/Cache/Models/Message/IMessageActivityModel.cs
  8. +26
    -2
      src/Discord.Net.Core/Cache/Models/Message/IMessageModel.cs
  9. +14
    -0
      src/Discord.Net.Core/Cache/Models/Message/IReactionModel.cs
  10. +15
    -0
      src/Discord.Net.Core/Cache/Models/Message/IStickerModel.cs
  11. +5
    -0
      src/Discord.Net.Core/Entities/Messages/IMessage.cs
  12. +25
    -3
      src/Discord.Net.Core/Entities/Messages/MessageInteraction.cs
  13. +4
    -0
      src/Discord.Net.Core/Utils/Optional.cs
  14. +23
    -1
      src/Discord.Net.Rest/API/Common/ActionRowComponent.cs
  15. +12
    -1
      src/Discord.Net.Rest/API/Common/Attachment.cs
  16. +23
    -1
      src/Discord.Net.Rest/API/Common/ButtonComponent.cs
  17. +11
    -1
      src/Discord.Net.Rest/API/Common/Embed.cs
  18. +1
    -1
      src/Discord.Net.Rest/API/Common/EmbedAuthor.cs
  19. +2
    -2
      src/Discord.Net.Rest/API/Common/EmbedField.cs
  20. +1
    -1
      src/Discord.Net.Rest/API/Common/EmbedFooter.cs
  21. +4
    -1
      src/Discord.Net.Rest/API/Common/EmbedImage.cs
  22. +1
    -1
      src/Discord.Net.Rest/API/Common/EmbedProvider.cs
  23. +4
    -1
      src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs
  24. +6
    -1
      src/Discord.Net.Rest/API/Common/EmbedVideo.cs
  25. +34
    -1
      src/Discord.Net.Rest/API/Common/Message.cs
  26. +4
    -1
      src/Discord.Net.Rest/API/Common/MessageActivity.cs
  27. +6
    -1
      src/Discord.Net.Rest/API/Common/MessageApplication.cs
  28. +6
    -2
      src/Discord.Net.Rest/API/Common/Reaction.cs
  29. +24
    -1
      src/Discord.Net.Rest/API/Common/SelectMenuComponent.cs
  30. +9
    -1
      src/Discord.Net.Rest/API/Common/SelectMenuOption.cs
  31. +6
    -1
      src/Discord.Net.Rest/API/Common/StickerItem.cs
  32. +25
    -1
      src/Discord.Net.Rest/API/Common/TextInputComponent.cs
  33. +10
    -0
      src/Discord.Net.Rest/ClientHelper.cs
  34. +3
    -0
      src/Discord.Net.Rest/DiscordRestClient.cs
  35. +6
    -6
      src/Discord.Net.Rest/Entities/Messages/Attachment.cs
  36. +4
    -6
      src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs
  37. +19
    -40
      src/Discord.Net.Rest/Extensions/EntityExtensions.cs
  38. +110
    -39
      src/Discord.Net.WebSocket/ClientStateManager.Experiment.cs
  39. +356
    -57
      src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs
  40. +33
    -22
      src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs
  41. +22
    -50
      src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs
  42. +2
    -2
      src/Discord.Net.WebSocket/Entities/Stickers/SocketUnknownSticker.cs
  43. +18
    -4
      src/Discord.Net.WebSocket/Extensions/CacheModelExtensions.cs
  44. +16
    -0
      src/Discord.Net.WebSocket/Extensions/EntityCacheExtensions.cs
  45. +12
    -0
      src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs

+ 12
- 7
src/Discord.Net.Core/Cache/CacheableEntityExtensions.cs View File

@@ -78,17 +78,14 @@ namespace Discord
};
}

public static IEmojiModel ToModel<TModel>(this IEmote emote) where TModel : IEmojiModel, new()
public static IEmojiModel ToModel(this IEmote emote, IEmojiModel model)
{
if (emote == null)
return null;

var model = new TModel()
{
Name = emote.Name
};
model.Name = emote.Name;

if(emote is GuildEmote guildEmote)
if (emote is GuildEmote guildEmote)
{
model.Id = guildEmote.Id;
model.IsAnimated = guildEmote.Animated;
@@ -99,7 +96,7 @@ namespace Discord
model.Roles = guildEmote.RoleIds.ToArray();
}

if(emote is Emote e)
if (emote is Emote e)
{
model.IsAnimated = e.Animated;
model.Id = e.Id;
@@ -107,5 +104,13 @@ namespace Discord

return model;
}

public static IEmojiModel ToModel<TModel>(this IEmote emote) where TModel : IEmojiModel, new()
{
if (emote == null)
return null;

return emote.ToModel(new TModel());
}
}
}

+ 16
- 0
src/Discord.Net.Core/Cache/Models/Application/IPartialApplicationModel.cs View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
public interface IPartialApplicationModel : IEntityModel<ulong>
{
string Name { get; set; }
string Icon { get; set; }
string Description { get; set; }
string CoverImage { get; set; }
}
}

+ 35
- 0
src/Discord.Net.Core/Cache/Models/Message/Components/IMessageComponentModel.cs View File

@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
public interface IMessageComponentModel
{
ComponentType Type { get; set; }
string CustomId { get; set; }
bool? Disabled { get; set; }
ButtonStyle? Style { get; set; }
string Label { get; set; }

// emoji
ulong? EmojiId { get; set; }
string EmojiName { get; set; }
bool? EmojiAnimated { get; set; }

string Url { get; set; }

IMessageComponentOptionModel[] Options { get; set; }

string Placeholder { get; set; }
int? MinValues { get; set; }
int? MaxValues { get; set; }
IMessageComponentModel[] Components { get; set; }
int? MinLength { get; set; }
int? MaxLength { get; set; }
bool? Required { get; set; }
string Value { get; set; }
}
}

+ 22
- 0
src/Discord.Net.Core/Cache/Models/Message/Components/IMessageComponentOptionModel.cs View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
public interface IMessageComponentOptionModel
{
string Label { get; set; }
string Value { get; set; }
string Description { get; set; }

// emoji
ulong? EmojiId { get; set; }
string EmojiName { get; set; }
bool? EmojiAnimated { get; set; }

bool? Default { get; set; }
}
}

+ 21
- 0
src/Discord.Net.Core/Cache/Models/Message/IAttachmentModel.cs View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
public interface IAttachmentModel : IEntityModel<ulong>
{
string FileName { get; set; }
string Description { get; set; }
string ContentType { get; set; }
int Size { get; set; }
string Url { get; set; }
string ProxyUrl { get; set; }
int? Height { get; set; }
int? Width { get; set; }
bool Ephemeral { get; set; }
}
}

+ 44
- 0
src/Discord.Net.Core/Cache/Models/Message/IEmbedModel.cs View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
public interface IEmbedModel
{
string Title { get; set; }
EmbedType Type { get; set; }
string Description { get; set; }
string Url { get; set; }
long? Timestamp { get; set; }
uint? Color { get; set; }
string FooterText { get; set; }
string FooterIconUrl { get; set; }
string FooterProxyUrl { get; set; }
string ProviderName { get; set; }
string ProviderUrl { get; set; }
string AuthorName { get; set; }
string AuthorUrl { get; set; }
string AuthorIconUrl { get; set; }
string AuthorProxyIconUrl { get; set; }
IEmbedMediaModel Image { get; set; }
IEmbedMediaModel Thumbnail { get; set; }
IEmbedMediaModel Video { get; set; }
IEmbedFieldModel[] Fields { get; set; }
}
public interface IEmbedMediaModel
{
string Url { get; set; }
string ProxyUrl { get; set; }
int? Height { get; set; }
int? Width { get; set; }
}
public interface IEmbedFieldModel
{
string Name { get; set; }
string Value { get; set; }
bool Inline { get; set; }
}
}

+ 14
- 0
src/Discord.Net.Core/Cache/Models/Message/IMessageActivityModel.cs View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
public interface IMessageActivityModel
{
MessageActivityType? Type { get; set; }
string PartyId { get; set; }
}
}

+ 26
- 2
src/Discord.Net.Core/Cache/Models/Message/IMessageModel.cs View File

@@ -14,11 +14,35 @@ namespace Discord
ulong AuthorId { get; set; }
bool IsWebhookMessage { get; set; }
string Content { get; set; }
DateTimeOffset Timestamp { get; set; }
DateTimeOffset? EditedTimestamp { get; set; }
long Timestamp { get; set; }
long? EditedTimestamp { get; set; }
bool IsTextToSpeech { get; set; }
bool MentionEveryone { get; set; }
ulong[] UserMentionIds { get; set; }
ulong[] RoleMentionIds { get; set; }

IAttachmentModel[] Attachments { get; set; }
IEmbedModel[] Embeds { get; set; }
IReactionMetadataModel[] Reactions { get; set; }
bool Pinned { get; set; }
IMessageActivityModel Activity { get; set; }
IPartialApplicationModel Application { get; set; }
ulong? ApplicationId { get; set; }

// message reference
ulong? ReferenceMessageId { get; set; }
ulong? ReferenceMessageChannelId { get; set; }
ulong? ReferenceMessageGuildId { get; set; }

MessageFlags Flags { get; set; }

// interaction
ulong? InteractionId { get; set; }
string InteractionName { get; set; }
InteractionType? InteractionType { get; set; }
ulong? InteractionUserId { get; set; }

IMessageComponentModel[] Components { get; set; }
IStickerItemModel[] Stickers { get; set; }
}
}

+ 14
- 0
src/Discord.Net.Core/Cache/Models/Message/IReactionModel.cs View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
public interface IReactionMetadataModel
{
IEmojiModel Emoji { get; set; }
ulong[] Users { get; set; }
}
}

+ 15
- 0
src/Discord.Net.Core/Cache/Models/Message/IStickerModel.cs View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
public interface IStickerItemModel
{
ulong Id { get; set; }
string Name { get; set; }
StickerFormatType Format { get; set; }
}
}

+ 5
- 0
src/Discord.Net.Core/Entities/Messages/IMessage.cs View File

@@ -46,6 +46,11 @@ namespace Discord
/// </returns>
bool MentionedEveryone { get; }
/// <summary>
/// If the message is a <see cref="MessageType.ApplicationCommand"/> or application-owned webhook,
/// this is the id of the application.
/// </summary>
ulong? ApplicationId { get; }
/// <summary>
/// Gets the content for this message.
/// </summary>
/// <returns>


+ 25
- 3
src/Discord.Net.Core/Entities/Messages/MessageInteraction.cs View File

@@ -10,7 +10,7 @@ namespace Discord
/// Represents a partial <see cref="IDiscordInteraction"/> within a message.
/// </summary>
/// <typeparam name="TUser">The type of the user.</typeparam>
public class MessageInteraction<TUser> : IMessageInteraction where TUser : IUser
public class MessageInteraction<TUser> : IMessageInteraction where TUser : class, IUser
{
/// <summary>
/// Gets the snowflake id of the interaction.
@@ -30,14 +30,36 @@ namespace Discord
/// <summary>
/// Gets the <typeparamref name="TUser"/> who invoked the interaction.
/// </summary>
public TUser User { get; }
/// <remarks>
/// When this property is a SocketUser, the get accessor will attempt to preform a
/// synchronous cache lookup.
/// </remarks>
public TUser User
=> _user ?? (_userLookup != null ? _userLookup(UserId) : null);

/// <summary>
/// Gets the id of the user who invoked the interaction.
/// </summary>
public ulong UserId { get; }

private readonly TUser _user;
private readonly Func<ulong, TUser> _userLookup;
internal MessageInteraction(ulong id, InteractionType type, string name, TUser user)
{
Id = id;
Type = type;
Name = name;
User = user;
_user = user;
UserId = user.Id;
}

internal MessageInteraction(ulong id, InteractionType type, string name, ulong userId, Func<ulong, TUser> lookup)
{
Id = id;
Type = type;
Name = name;
UserId = userId;
_userLookup = lookup;
}

IUser IMessageInteraction.User => User;


+ 4
- 0
src/Discord.Net.Core/Utils/Optional.cs View File

@@ -56,5 +56,9 @@ namespace Discord
public static T? ToNullable<T>(this Optional<T> val)
where T : struct
=> val.IsSpecified ? val.Value : null;

public static Optional<T> ToOptional<T>(this T? value)
where T : struct
=> value.HasValue ? new Optional<T>(value.Value) : new();
}
}

+ 23
- 1
src/Discord.Net.Rest/API/Common/ActionRowComponent.cs View File

@@ -3,7 +3,7 @@ using System.Linq;

namespace Discord.API
{
internal class ActionRowComponent : IMessageComponent
internal class ActionRowComponent : IMessageComponent, IMessageComponentModel
{
[JsonProperty("type")]
public ComponentType Type { get; set; }
@@ -29,5 +29,27 @@ namespace Discord.API

[JsonIgnore]
string IMessageComponent.CustomId => null;

ComponentType IMessageComponentModel.Type { get => Type; set => throw new System.NotSupportedException(); }
IMessageComponentModel[] IMessageComponentModel.Components { get => Components.Select(x => x as IMessageComponentModel).ToArray(); set => throw new System.NotSupportedException(); } // cursed hack here

#region unused
string IMessageComponentModel.CustomId { get => null; set => throw new System.NotSupportedException(); }
bool? IMessageComponentModel.Disabled { get => null; set => throw new System.NotSupportedException(); }
ButtonStyle? IMessageComponentModel.Style { get => null; set => throw new System.NotSupportedException(); }
string IMessageComponentModel.Label { get => null; set => throw new System.NotSupportedException(); }
ulong? IMessageComponentModel.EmojiId { get => null; set => throw new System.NotSupportedException(); }
string IMessageComponentModel.EmojiName { get => null; set => throw new System.NotSupportedException(); }
bool? IMessageComponentModel.EmojiAnimated { get => null; set => throw new System.NotSupportedException(); }
string IMessageComponentModel.Url { get => null; set => throw new System.NotSupportedException(); }
IMessageComponentOptionModel[] IMessageComponentModel.Options { get => null; set => throw new System.NotSupportedException(); }
string IMessageComponentModel.Placeholder { get => null; set => throw new System.NotSupportedException(); }
int? IMessageComponentModel.MinValues { get => null; set => throw new System.NotSupportedException(); }
int? IMessageComponentModel.MaxValues { get => null; set => throw new System.NotSupportedException(); }
int? IMessageComponentModel.MinLength { get => null; set => throw new System.NotSupportedException(); }
int? IMessageComponentModel.MaxLength { get => null; set => throw new System.NotSupportedException(); }
bool? IMessageComponentModel.Required { get => null; set => throw new System.NotSupportedException(); }
string IMessageComponentModel.Value { get => null; set => throw new System.NotSupportedException(); }
#endregion
}
}

+ 12
- 1
src/Discord.Net.Rest/API/Common/Attachment.cs View File

@@ -2,7 +2,7 @@ using Newtonsoft.Json;

namespace Discord.API
{
internal class Attachment
internal class Attachment : IAttachmentModel
{
[JsonProperty("id")]
public ulong Id { get; set; }
@@ -24,5 +24,16 @@ namespace Discord.API
public Optional<int> Width { get; set; }
[JsonProperty("ephemeral")]
public Optional<bool> Ephemeral { get; set; }

string IAttachmentModel.FileName { get => Filename; set => throw new System.NotSupportedException(); }
string IAttachmentModel.Description { get => Description.GetValueOrDefault(); set => throw new System.NotSupportedException(); }
string IAttachmentModel.ContentType { get => ContentType.GetValueOrDefault(); set => throw new System.NotSupportedException(); }
int IAttachmentModel.Size { get => Size; set => throw new System.NotSupportedException(); }
string IAttachmentModel.Url { get => Url; set => throw new System.NotSupportedException(); }
string IAttachmentModel.ProxyUrl { get => ProxyUrl; set => throw new System.NotSupportedException(); }
int? IAttachmentModel.Height { get => Height.ToNullable(); set => throw new System.NotSupportedException(); }
int? IAttachmentModel.Width { get => Width.ToNullable(); set => throw new System.NotSupportedException(); }
bool IAttachmentModel.Ephemeral { get => Ephemeral.GetValueOrDefault(); set => throw new System.NotSupportedException(); }
ulong IEntityModel<ulong>.Id { get => Id; set => throw new System.NotSupportedException(); }
}
}

+ 23
- 1
src/Discord.Net.Rest/API/Common/ButtonComponent.cs View File

@@ -2,7 +2,7 @@ using Newtonsoft.Json;

namespace Discord.API
{
internal class ButtonComponent : IMessageComponent
internal class ButtonComponent : IMessageComponent, IMessageComponentModel
{
[JsonProperty("type")]
public ComponentType Type { get; set; }
@@ -59,5 +59,27 @@ namespace Discord.API

[JsonIgnore]
string IMessageComponent.CustomId => CustomId.GetValueOrDefault();

ComponentType IMessageComponentModel.Type { get => Type; set => throw new System.NotSupportedException(); }
string IMessageComponentModel.CustomId { get => CustomId.GetValueOrDefault(); set => throw new System.NotSupportedException(); }
bool? IMessageComponentModel.Disabled { get => Disabled.ToNullable(); set => throw new System.NotSupportedException(); }
ButtonStyle? IMessageComponentModel.Style { get => Style; set => throw new System.NotSupportedException(); }
string IMessageComponentModel.Label { get => Label.GetValueOrDefault(); set => throw new System.NotSupportedException(); }
ulong? IMessageComponentModel.EmojiId { get => Emote.GetValueOrDefault()?.Id; set => throw new System.NotSupportedException(); }
string IMessageComponentModel.EmojiName { get => Emote.GetValueOrDefault()?.Name; set => throw new System.NotSupportedException(); }
bool? IMessageComponentModel.EmojiAnimated { get => Emote.GetValueOrDefault()?.Animated; set => throw new System.NotSupportedException(); }
string IMessageComponentModel.Url { get => Url.GetValueOrDefault(); set => throw new System.NotSupportedException(); }

#region unused
IMessageComponentOptionModel[] IMessageComponentModel.Options { get => null; set => throw new System.NotSupportedException(); }
string IMessageComponentModel.Placeholder { get => null; set => throw new System.NotSupportedException(); }
int? IMessageComponentModel.MinValues { get => null; set => throw new System.NotSupportedException(); }
int? IMessageComponentModel.MaxValues { get => null; set => throw new System.NotSupportedException(); }
IMessageComponentModel[] IMessageComponentModel.Components { get => null; set => throw new System.NotSupportedException(); }
int? IMessageComponentModel.MinLength { get => null; set => throw new System.NotSupportedException(); }
int? IMessageComponentModel.MaxLength { get => null; set => throw new System.NotSupportedException(); }
bool? IMessageComponentModel.Required { get => null; set => throw new System.NotSupportedException(); }
string IMessageComponentModel.Value { get => null; set => throw new System.NotSupportedException(); }
#endregion
}
}

+ 11
- 1
src/Discord.Net.Rest/API/Common/Embed.cs View File

@@ -4,7 +4,7 @@ using Discord.Net.Converters;

namespace Discord.API
{
internal class Embed
internal class Embed : IEmbedModel
{
[JsonProperty("title")]
public string Title { get; set; }
@@ -32,5 +32,15 @@ namespace Discord.API
public Optional<EmbedProvider> Provider { get; set; }
[JsonProperty("fields")]
public Optional<EmbedField[]> Fields { get; set; }

EmbedType IEmbedModel.Type { get => Type; set => throw new NotSupportedException(); }
DateTimeOffset? IEmbedModel.Timestamp { get => Timestamp; set => throw new NotSupportedException(); }
IEmbedFooterModel IEmbedModel.Footer { get => Footer.GetValueOrDefault(); set => throw new NotSupportedException(); }
IEmbedMediaModel IEmbedModel.Image { get => Image.GetValueOrDefault(); set => throw new NotSupportedException(); }
IEmbedMediaModel IEmbedModel.Thumbnail { get => Thumbnail.GetValueOrDefault(); set => throw new NotSupportedException(); }
IEmbedMediaModel IEmbedModel.Video { get => Video.GetValueOrDefault(); set => throw new NotSupportedException(); }
IEmbedProviderModel IEmbedModel.Provider { get => Provider.GetValueOrDefault(); set => throw new NotSupportedException(); }
IEmbedAuthorModel IEmbedModel.Author { get => Author.GetValueOrDefault(); set => throw new NotSupportedException(); }
IEmbedFieldModel[] IEmbedModel.Fields { get => Fields.GetValueOrDefault(); set => throw new NotSupportedException(); }
}
}

+ 1
- 1
src/Discord.Net.Rest/API/Common/EmbedAuthor.cs View File

@@ -2,7 +2,7 @@ using Newtonsoft.Json;

namespace Discord.API
{
internal class EmbedAuthor
internal class EmbedAuthor : IEmbedAuthorModel
{
[JsonProperty("name")]
public string Name { get; set; }


+ 2
- 2
src/Discord.Net.Rest/API/Common/EmbedField.cs View File

@@ -1,8 +1,8 @@
using Newtonsoft.Json;
using Newtonsoft.Json;

namespace Discord.API
{
internal class EmbedField
internal class EmbedField : IEmbedFieldModel
{
[JsonProperty("name")]
public string Name { get; set; }


+ 1
- 1
src/Discord.Net.Rest/API/Common/EmbedFooter.cs View File

@@ -2,7 +2,7 @@ using Newtonsoft.Json;

namespace Discord.API
{
internal class EmbedFooter
internal class EmbedFooter : IEmbedFooterModel
{
[JsonProperty("text")]
public string Text { get; set; }


+ 4
- 1
src/Discord.Net.Rest/API/Common/EmbedImage.cs View File

@@ -2,7 +2,7 @@ using Newtonsoft.Json;

namespace Discord.API
{
internal class EmbedImage
internal class EmbedImage : IEmbedMediaModel
{
[JsonProperty("url")]
public string Url { get; set; }
@@ -12,5 +12,8 @@ namespace Discord.API
public Optional<int> Height { get; set; }
[JsonProperty("width")]
public Optional<int> Width { get; set; }

int? IEmbedMediaModel.Height { get => Height.ToNullable(); set => throw new System.NotSupportedException(); }
int? IEmbedMediaModel.Width { get => Width.ToNullable(); set => throw new System.NotSupportedException(); }
}
}

+ 1
- 1
src/Discord.Net.Rest/API/Common/EmbedProvider.cs View File

@@ -2,7 +2,7 @@ using Newtonsoft.Json;

namespace Discord.API
{
internal class EmbedProvider
internal class EmbedProvider : IEmbedProviderModel
{
[JsonProperty("name")]
public string Name { get; set; }


+ 4
- 1
src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs View File

@@ -2,7 +2,7 @@ using Newtonsoft.Json;

namespace Discord.API
{
internal class EmbedThumbnail
internal class EmbedThumbnail : IEmbedMediaModel
{
[JsonProperty("url")]
public string Url { get; set; }
@@ -12,5 +12,8 @@ namespace Discord.API
public Optional<int> Height { get; set; }
[JsonProperty("width")]
public Optional<int> Width { get; set; }

int? IEmbedMediaModel.Height { get => Height.ToNullable(); set => throw new System.NotSupportedException(); }
int? IEmbedMediaModel.Width { get => Width.ToNullable(); set => throw new System.NotSupportedException(); }
}
}

+ 6
- 1
src/Discord.Net.Rest/API/Common/EmbedVideo.cs View File

@@ -2,13 +2,18 @@ using Newtonsoft.Json;

namespace Discord.API
{
internal class EmbedVideo
internal class EmbedVideo : IEmbedMediaModel
{
[JsonProperty("url")]
public string Url { get; set; }
[JsonProperty("proxy_url")]
public string ProxyUrl { get; set; }
[JsonProperty("height")]
public Optional<int> Height { get; set; }
[JsonProperty("width")]
public Optional<int> Width { get; set; }

int? IEmbedMediaModel.Height { get => Height.ToNullable(); set => throw new System.NotSupportedException(); }
int? IEmbedMediaModel.Width { get => Width.ToNullable(); set => throw new System.NotSupportedException(); }
}
}

+ 34
- 1
src/Discord.Net.Rest/API/Common/Message.cs View File

@@ -1,9 +1,10 @@
using Newtonsoft.Json;
using System;
using System.Linq;

namespace Discord.API
{
internal class Message
internal class Message : IMessageModel
{
[JsonProperty("id")]
public ulong Id { get; set; }
@@ -49,6 +50,8 @@ namespace Discord.API
// sent with Rich Presence-related chat embeds
[JsonProperty("application")]
public Optional<MessageApplication> Application { get; set; }
[JsonProperty("application_id")]
public Optional<ulong> ApplicationId { get; set; }
[JsonProperty("message_reference")]
public Optional<MessageReference> Reference { get; set; }
[JsonProperty("flags")]
@@ -62,5 +65,35 @@ namespace Discord.API
public Optional<MessageInteraction> Interaction { get; set; }
[JsonProperty("sticker_items")]
public Optional<StickerItem[]> StickerItems { get; set; }


MessageType IMessageModel.Type { get => Type; set => throw new NotSupportedException(); }
ulong IMessageModel.ChannelId { get => ChannelId; set => throw new NotSupportedException(); }
ulong? IMessageModel.GuildId { get => GuildId.ToNullable(); set => throw new NotSupportedException(); }
ulong IMessageModel.AuthorId { get => Author.IsSpecified ? Author.Value.Id : Member.IsSpecified ? Member.Value.User.Id : WebhookId.GetValueOrDefault(); set => throw new NotSupportedException(); }
bool IMessageModel.IsWebhookMessage { get => WebhookId.IsSpecified; set => throw new NotSupportedException(); }
string IMessageModel.Content { get => Content.GetValueOrDefault(); set => throw new NotSupportedException(); }
DateTimeOffset IMessageModel.Timestamp { get => Timestamp.Value; set => throw new NotSupportedException(); } // might break?
DateTimeOffset? IMessageModel.EditedTimestamp { get => Timestamp.ToNullable(); set => throw new NotSupportedException(); }
bool IMessageModel.IsTextToSpeech { get => IsTextToSpeech.GetValueOrDefault(); set => throw new NotSupportedException(); }
bool IMessageModel.MentionEveryone { get => MentionEveryone.GetValueOrDefault(); set => throw new NotSupportedException(); }
ulong[] IMessageModel.UserMentionIds { get => UserMentions.IsSpecified ? UserMentions.Value.Select(x => x.Id).ToArray() : Array.Empty<ulong>(); set => throw new NotSupportedException(); }
IAttachmentModel[] IMessageModel.Attachments { get => Attachments.GetValueOrDefault(Array.Empty<Attachment>()); set => throw new NotSupportedException(); }
IEmbedModel[] IMessageModel.Embeds { get => Embeds.GetValueOrDefault(Array.Empty<Embed>()); set => throw new NotSupportedException(); }
IReactionMetadataModel[] IMessageModel.Reactions { get => Reactions.GetValueOrDefault(Array.Empty<Reaction>()); set => throw new NotSupportedException(); }
bool IMessageModel.Pinned { get => Pinned.GetValueOrDefault(); set => throw new NotSupportedException(); }
IMessageActivityModel IMessageModel.Activity { get => Activity.GetValueOrDefault(); set => throw new NotSupportedException(); }
IPartialApplicationModel IMessageModel.Application { get => Application.GetValueOrDefault(); set => throw new NotSupportedException(); }
ulong? IMessageModel.ApplicationId { get => ApplicationId.ToNullable(); set => throw new NotSupportedException(); }
ulong? IMessageModel.ReferenceMessageId { get => ReferencedMessage.GetValueOrDefault()?.Id; set => throw new NotSupportedException(); }
ulong? IMessageModel.ReferenceMessageChannelId { get => ReferencedMessage.GetValueOrDefault()?.ChannelId; set => throw new NotSupportedException(); }
MessageFlags IMessageModel.Flags { get => Flags.GetValueOrDefault(); set => throw new NotSupportedException(); }
ulong? IMessageModel.InteractionId { get => Interaction.GetValueOrDefault()?.Id; set => throw new NotSupportedException(); }
string IMessageModel.InteractionName { get => Interaction.GetValueOrDefault()?.Name; set => throw new NotSupportedException(); }
InteractionType? IMessageModel.InteractionType { get => Interaction.GetValueOrDefault()?.Type; set => throw new NotSupportedException(); }
ulong? IMessageModel.InteractionUserId { get => Interaction.GetValueOrDefault()?.User.Id; set => throw new NotSupportedException(); }
IMessageComponentModel[] IMessageModel.Components { get => Components.GetValueOrDefault(Array.Empty<ActionRowComponent>()); set => throw new NotSupportedException(); }
IStickerItemModel[] IMessageModel.Stickers { get => StickerItems.GetValueOrDefault(Array.Empty<StickerItem>()); set => throw new NotSupportedException(); }
ulong IEntityModel<ulong>.Id { get => Id; set => throw new NotSupportedException(); }
}
}

+ 4
- 1
src/Discord.Net.Rest/API/Common/MessageActivity.cs View File

@@ -7,11 +7,14 @@ using System.Threading.Tasks;

namespace Discord.API
{
public class MessageActivity
public class MessageActivity : IMessageActivityModel
{
[JsonProperty("type")]
public Optional<MessageActivityType> Type { get; set; }
[JsonProperty("party_id")]
public Optional<string> PartyId { get; set; }

MessageActivityType? IMessageActivityModel.Type { get => Type.ToNullable(); set => throw new NotSupportedException(); }
string IMessageActivityModel.PartyId { get => PartyId.GetValueOrDefault(); set => throw new NotSupportedException(); }
}
}

+ 6
- 1
src/Discord.Net.Rest/API/Common/MessageApplication.cs View File

@@ -7,7 +7,7 @@ using System.Threading.Tasks;

namespace Discord.API
{
public class MessageApplication
internal class MessageApplication : IPartialApplicationModel
{
/// <summary>
/// Gets the snowflake ID of the application.
@@ -34,5 +34,10 @@ namespace Discord.API
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }

string IPartialApplicationModel.CoverImage { get => CoverImage; set => throw new NotSupportedException(); }
string IPartialApplicationModel.Icon { get => Icon; set => throw new NotSupportedException(); }
string IPartialApplicationModel.Name { get => Name; set => throw new NotSupportedException(); }
ulong IEntityModel<ulong>.Id { get => Id; set => throw new NotSupportedException(); }
}
}

+ 6
- 2
src/Discord.Net.Rest/API/Common/Reaction.cs View File

@@ -1,8 +1,8 @@
using Newtonsoft.Json;
using Newtonsoft.Json;

namespace Discord.API
{
internal class Reaction
internal class Reaction : IReactionMetadataModel
{
[JsonProperty("count")]
public int Count { get; set; }
@@ -10,5 +10,9 @@ namespace Discord.API
public bool Me { get; set; }
[JsonProperty("emoji")]
public Emoji Emoji { get; set; }

int IReactionMetadataModel.Count { get => Count; set => throw new System.NotSupportedException(); }
bool IReactionMetadataModel.Me { get => Me; set => throw new System.NotSupportedException(); }
IEmojiModel IReactionMetadataModel.Emoji { get => Emoji; set => throw new System.NotSupportedException(); }
}
}

+ 24
- 1
src/Discord.Net.Rest/API/Common/SelectMenuComponent.cs View File

@@ -3,7 +3,7 @@ using System.Linq;

namespace Discord.API
{
internal class SelectMenuComponent : IMessageComponent
internal class SelectMenuComponent : IMessageComponent, IMessageComponentModel
{
[JsonProperty("type")]
public ComponentType Type { get; set; }
@@ -28,6 +28,7 @@ namespace Discord.API

[JsonProperty("values")]
public Optional<string[]> Values { get; set; }

public SelectMenuComponent() { }

public SelectMenuComponent(Discord.SelectMenuComponent component)
@@ -40,5 +41,27 @@ namespace Discord.API
MaxValues = component.MaxValues;
Disabled = component.IsDisabled;
}

ComponentType IMessageComponentModel.Type { get => Type; set => throw new System.NotSupportedException(); }
string IMessageComponentModel.CustomId { get => CustomId; set => throw new System.NotSupportedException(); }
bool? IMessageComponentModel.Disabled { get => Disabled; set => throw new System.NotSupportedException(); }
IMessageComponentOptionModel[] IMessageComponentModel.Options { get => Options; set => throw new System.NotSupportedException(); }
string IMessageComponentModel.Placeholder { get => Placeholder.GetValueOrDefault(); set => throw new System.NotSupportedException(); }
int? IMessageComponentModel.MinValues { get => MinValues; set => throw new System.NotSupportedException(); }
int? IMessageComponentModel.MaxValues { get => MaxValues; set => throw new System.NotSupportedException(); }

#region unused
ButtonStyle? IMessageComponentModel.Style { get => null; set => throw new System.NotSupportedException(); }
string IMessageComponentModel.Label { get => null; set => throw new System.NotSupportedException(); }
ulong? IMessageComponentModel.EmojiId { get => null; set => throw new System.NotSupportedException(); }
string IMessageComponentModel.EmojiName { get => null; set => throw new System.NotSupportedException(); }
bool? IMessageComponentModel.EmojiAnimated { get => null; set => throw new System.NotSupportedException(); }
string IMessageComponentModel.Url { get => null; set => throw new System.NotSupportedException(); }
IMessageComponentModel[] IMessageComponentModel.Components { get => null; set => throw new System.NotSupportedException(); }
int? IMessageComponentModel.MinLength { get => null; set => throw new System.NotSupportedException(); }
int? IMessageComponentModel.MaxLength { get => null; set => throw new System.NotSupportedException(); }
bool? IMessageComponentModel.Required { get => null; set => throw new System.NotSupportedException(); }
string IMessageComponentModel.Value { get => null; set => throw new System.NotSupportedException(); }
#endregion
}
}

+ 9
- 1
src/Discord.Net.Rest/API/Common/SelectMenuOption.cs View File

@@ -2,7 +2,7 @@ using Newtonsoft.Json;

namespace Discord.API
{
internal class SelectMenuOption
internal class SelectMenuOption : IMessageComponentOptionModel
{
[JsonProperty("label")]
public string Label { get; set; }
@@ -49,5 +49,13 @@ namespace Discord.API

Default = option.IsDefault ?? Optional<bool>.Unspecified;
}

string IMessageComponentOptionModel.Label { get => Label; set => throw new System.NotSupportedException(); }
string IMessageComponentOptionModel.Value { get => Value; set => throw new System.NotSupportedException(); }
string IMessageComponentOptionModel.Description { get => Description.GetValueOrDefault(); set => throw new System.NotSupportedException(); }
ulong? IMessageComponentOptionModel.EmojiId { get => Emoji.GetValueOrDefault()?.Id; set => throw new System.NotSupportedException(); }
string IMessageComponentOptionModel.EmojiName { get => Emoji.GetValueOrDefault()?.Name; set => throw new System.NotSupportedException(); }
bool? IMessageComponentOptionModel.EmojiAnimated { get => Emoji.GetValueOrDefault()?.Animated; set => throw new System.NotSupportedException(); }
bool? IMessageComponentOptionModel.Default { get => Default.ToNullable(); set => throw new System.NotSupportedException(); }
}
}

+ 6
- 1
src/Discord.Net.Rest/API/Common/StickerItem.cs View File

@@ -2,7 +2,7 @@ using Newtonsoft.Json;

namespace Discord.API
{
internal class StickerItem
internal class StickerItem : IStickerItemModel
{
[JsonProperty("id")]
public ulong Id { get; set; }
@@ -12,5 +12,10 @@ namespace Discord.API

[JsonProperty("format_type")]
public StickerFormatType FormatType { get; set; }


ulong IStickerItemModel.Id { get => Id; set => throw new System.NotSupportedException(); }
string IStickerItemModel.Name { get => Name; set => throw new System.NotSupportedException(); }
StickerFormatType IStickerItemModel.Format { get => FormatType; set => throw new System.NotSupportedException(); }
}
}

+ 25
- 1
src/Discord.Net.Rest/API/Common/TextInputComponent.cs View File

@@ -2,7 +2,7 @@ using Newtonsoft.Json;

namespace Discord.API
{
internal class TextInputComponent : IMessageComponent
internal class TextInputComponent : IMessageComponent, IMessageComponentModel
{
[JsonProperty("type")]
public ComponentType Type { get; set; }
@@ -45,5 +45,29 @@ namespace Discord.API
Required = component.Required ?? Optional<bool>.Unspecified;
Value = component.Value ?? Optional<string>.Unspecified;
}

ComponentType IMessageComponentModel.Type { get => Type; set => throw new System.NotSupportedException(); }
string IMessageComponentModel.CustomId { get => CustomId; set => throw new System.NotSupportedException(); }
int? IMessageComponentModel.MinLength { get => MinLength.ToNullable(); set => throw new System.NotSupportedException(); }
int? IMessageComponentModel.MaxLength { get => MaxLength.ToNullable(); set => throw new System.NotSupportedException(); }
bool? IMessageComponentModel.Required { get => Required.ToNullable(); set => throw new System.NotSupportedException(); }
string IMessageComponentModel.Value { get => Value.GetValueOrDefault(); set => throw new System.NotSupportedException(); }
string IMessageComponentModel.Label { get => Label; set => throw new System.NotSupportedException(); }
string IMessageComponentModel.Placeholder { get => Placeholder.GetValueOrDefault(); set => throw new System.NotSupportedException(); }

#region unused

bool? IMessageComponentModel.Disabled { get => null; set => throw new System.NotSupportedException(); }
ButtonStyle? IMessageComponentModel.Style { get => null; set => throw new System.NotSupportedException(); }
ulong? IMessageComponentModel.EmojiId { get => null; set => throw new System.NotSupportedException(); }
string IMessageComponentModel.EmojiName { get => null; set => throw new System.NotSupportedException(); }
bool? IMessageComponentModel.EmojiAnimated { get => null; set => throw new System.NotSupportedException(); }
string IMessageComponentModel.Url { get => null; set => throw new System.NotSupportedException(); }
IMessageComponentOptionModel[] IMessageComponentModel.Options { get => null; set => throw new System.NotSupportedException(); }
int? IMessageComponentModel.MinValues { get => null; set => throw new System.NotSupportedException(); }
int? IMessageComponentModel.MaxValues { get => null; set => throw new System.NotSupportedException(); }
IMessageComponentModel[] IMessageComponentModel.Components { get => null; set => throw new System.NotSupportedException(); }

#endregion
}
}

+ 10
- 0
src/Discord.Net.Rest/ClientHelper.cs View File

@@ -46,6 +46,16 @@ namespace Discord.Rest
.Select(x => RestGroupChannel.Create(client, x)).ToImmutableArray();
}

public static async Task<RestMessage> GetMessageAsync(BaseDiscordClient client, ulong channelId, ulong messageId, RequestOptions options)
{
var channel = await GetChannelAsync(client, channelId, options).ConfigureAwait(false);

if (channel is not IRestMessageChannel msgChannel)
return null;

return await msgChannel.GetMessageAsync(messageId, options).ConfigureAwait(false);
}

public static async Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync(BaseDiscordClient client, RequestOptions options)
{
var models = await client.ApiClient.GetMyConnectionsAsync(options).ConfigureAwait(false);


+ 3
- 0
src/Discord.Net.Rest/DiscordRestClient.cs View File

@@ -158,6 +158,9 @@ namespace Discord.Rest
public Task<IReadOnlyCollection<RestGroupChannel>> GetGroupChannelsAsync(RequestOptions options = null)
=> ClientHelper.GetGroupChannelsAsync(this, options);

public Task<RestMessage> GetMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null)
=> ClientHelper.GetMessageAsync(this, channelId, messageId, options);

public Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync(RequestOptions options = null)
=> ClientHelper.GetConnectionsAsync(this, options);



+ 6
- 6
src/Discord.Net.Rest/Entities/Messages/Attachment.cs View File

@@ -1,5 +1,5 @@
using System.Diagnostics;
using Model = Discord.API.Attachment;
using Model = Discord.IAttachmentModel;

namespace Discord
{
@@ -44,11 +44,11 @@ namespace Discord
}
internal static Attachment Create(Model model)
{
return new Attachment(model.Id, model.Filename, model.Url, model.ProxyUrl, model.Size,
model.Height.IsSpecified ? model.Height.Value : (int?)null,
model.Width.IsSpecified ? model.Width.Value : (int?)null,
model.Ephemeral.ToNullable(), model.Description.GetValueOrDefault(),
model.ContentType.GetValueOrDefault());
return new Attachment(model.Id, model.FileName, model.Url, model.ProxyUrl, model.Size,
model.Height,
model.Width,
model.Ephemeral, model.Description,
model.ContentType);
}

/// <summary>


+ 4
- 6
src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs View File

@@ -221,7 +221,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, IReadOnlyCollection<IUser> userMentions)
public static ImmutableArray<ITag> ParseTags(string text, IMessageChannel channel, IGuild guild, ulong[] userMentions)
{
var tags = ImmutableArray.CreateBuilder<ITag>();
int index = 0;
@@ -278,11 +278,9 @@ namespace Discord.Rest
IUser mentionedUser = null;
foreach (var mention in userMentions)
{
if (mention.Id == id)
if (mention == id)
{
mentionedUser = channel?.GetUserAsync(id, CacheMode.CacheOnly).GetAwaiter().GetResult();
if (mentionedUser == null)
mentionedUser = mention;
break;
}
}
@@ -372,11 +370,11 @@ namespace Discord.Rest
.ToImmutableArray();
}

public static MessageSource GetSource(Model msg)
public static MessageSource GetSource(IMessageModel msg)
{
if (msg.Type != MessageType.Default && msg.Type != MessageType.Reply)
return MessageSource.System;
else if (msg.WebhookId.IsSpecified)
else if (msg.IsWebhookMessage)
return MessageSource.Webhook;
else if (msg.Author.GetValueOrDefault()?.Bot.GetValueOrDefault(false) == true)
return MessageSource.Bot;


+ 19
- 40
src/Discord.Net.Rest/Extensions/EntityExtensions.cs View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
@@ -40,17 +41,25 @@ namespace Discord.Rest
ImmutableArray.Create(model.Roles),
model.User.IsSpecified ? model.User.Value.Id : (ulong?)null);

public static Embed ToEntity(this API.Embed model)
public static Embed ToEntity(this IEmbedModel model)
{
return new Embed(model.Type, model.Title, model.Description, model.Url, model.Timestamp,
return new Embed(model.Type, model.Title, model.Description, model.Url,
model.Timestamp.HasValue ? new DateTimeOffset(model.Timestamp.Value, TimeSpan.Zero) : null,
model.Color.HasValue ? new Color(model.Color.Value) : (Color?)null,
model.Image.IsSpecified ? model.Image.Value.ToEntity() : (EmbedImage?)null,
model.Video.IsSpecified ? model.Video.Value.ToEntity() : (EmbedVideo?)null,
model.Author.IsSpecified ? model.Author.Value.ToEntity() : (EmbedAuthor?)null,
model.Footer.IsSpecified ? model.Footer.Value.ToEntity() : (EmbedFooter?)null,
model.Provider.IsSpecified ? model.Provider.Value.ToEntity() : (EmbedProvider?)null,
model.Thumbnail.IsSpecified ? model.Thumbnail.Value.ToEntity() : (EmbedThumbnail?)null,
model.Fields.IsSpecified ? model.Fields.Value.Select(x => x.ToEntity()).ToImmutableArray() : ImmutableArray.Create<EmbedField>());
model.Image != null
? new EmbedImage(model.Image.Url, model.Image.ProxyUrl, model.Image.Height, model.Image.Width) : (EmbedImage?)null,
model.Video != null
? new EmbedVideo(model.Video.Url, model.Video.Height, model.Video.Width) : (EmbedVideo?)null,
model.AuthorIconUrl != null || model.AuthorName != null || model.AuthorProxyIconUrl != null || model.AuthorUrl != null
? new EmbedAuthor(model.AuthorName, model.AuthorUrl, model.AuthorIconUrl, model.AuthorProxyIconUrl) : (EmbedAuthor?)null,
model.FooterIconUrl != null || model.FooterProxyUrl != null || model.FooterText != null
? new EmbedFooter(model.FooterText, model.FooterIconUrl, model.FooterProxyUrl) : (EmbedFooter?)null,
model.ProviderUrl != null || model.ProviderName != null
? new EmbedProvider(model.ProviderName, model.ProviderUrl) : (EmbedProvider?)null,
model.Thumbnail != null
? new EmbedThumbnail(model.Thumbnail.Url, model.Thumbnail.ProxyUrl, model.Thumbnail.Height, model.Thumbnail.Width) : (EmbedThumbnail?)null,
model.Fields != null
? model.Fields.Select(x => x.ToEntity()).ToImmutableArray() : ImmutableArray.Create<EmbedField>());
}
public static RoleTags ToEntity(this API.RoleTags model)
{
@@ -116,15 +125,11 @@ namespace Discord.Rest
if (mentionTypes.HasFlag(AllowedMentionTypes.Users))
yield return "users";
}
public static EmbedAuthor ToEntity(this API.EmbedAuthor model)
{
return new EmbedAuthor(model.Name, model.Url, model.IconUrl, model.ProxyIconUrl);
}
public static API.EmbedAuthor ToModel(this EmbedAuthor entity)
{
return new API.EmbedAuthor { Name = entity.Name, Url = entity.Url, IconUrl = entity.IconUrl };
}
public static EmbedField ToEntity(this API.EmbedField model)
public static EmbedField ToEntity(this IEmbedFieldModel model)
{
return new EmbedField(model.Name, model.Value, model.Inline);
}
@@ -132,48 +137,22 @@ namespace Discord.Rest
{
return new API.EmbedField { Name = entity.Name, Value = entity.Value, Inline = entity.Inline };
}
public static EmbedFooter ToEntity(this API.EmbedFooter model)
{
return new EmbedFooter(model.Text, model.IconUrl, model.ProxyIconUrl);
}
public static API.EmbedFooter ToModel(this EmbedFooter entity)
{
return new API.EmbedFooter { Text = entity.Text, IconUrl = entity.IconUrl };
}
public static EmbedImage ToEntity(this API.EmbedImage model)
{
return new EmbedImage(model.Url, model.ProxyUrl,
model.Height.IsSpecified ? model.Height.Value : (int?)null,
model.Width.IsSpecified ? model.Width.Value : (int?)null);
}
public static API.EmbedImage ToModel(this EmbedImage entity)
{
return new API.EmbedImage { Url = entity.Url };
}
public static EmbedProvider ToEntity(this API.EmbedProvider model)
{
return new EmbedProvider(model.Name, model.Url);
}
public static API.EmbedProvider ToModel(this EmbedProvider entity)
{
return new API.EmbedProvider { Name = entity.Name, Url = entity.Url };
}
public static EmbedThumbnail ToEntity(this API.EmbedThumbnail model)
{
return new EmbedThumbnail(model.Url, model.ProxyUrl,
model.Height.IsSpecified ? model.Height.Value : (int?)null,
model.Width.IsSpecified ? model.Width.Value : (int?)null);
}
public static API.EmbedThumbnail ToModel(this EmbedThumbnail entity)
{
return new API.EmbedThumbnail { Url = entity.Url };
}
public static EmbedVideo ToEntity(this API.EmbedVideo model)
{
return new EmbedVideo(model.Url,
model.Height.IsSpecified ? model.Height.Value : (int?)null,
model.Width.IsSpecified ? model.Width.Value : (int?)null);
}
public static API.EmbedVideo ToModel(this EmbedVideo entity)
{
return new API.EmbedVideo { Url = entity.Url };


+ 110
- 39
src/Discord.Net.WebSocket/ClientStateManager.Experiment.cs View File

@@ -49,7 +49,7 @@ namespace Discord.WebSocket

internal class ReferenceStore<TEntity, TModel, TId, TSharedEntity> : ILookupReferenceStore<TEntity, TId>
where TEntity : class, ICached<TModel>, TSharedEntity
where TModel : IEntityModel<TId>
where TModel : class, IEntityModel<TId>
where TId : IEquatable<TId>
where TSharedEntity : class
{
@@ -263,20 +263,40 @@ namespace Discord.WebSocket
return _store.PurgeAllAsync();
}

public IEnumerable<TEntity> GetEnumerable(IEnumerable<TId> ids)
{
foreach (var id in ids)
{
yield return Get(id);
}
}

public async IAsyncEnumerable<TEntity> GetEnumerableAsync(IEnumerable<TId> ids)
{
foreach (var id in ids)
{
yield return (TEntity)await GetAsync(id, CacheMode.CacheOnly);
}
}

TEntity ILookupReferenceStore<TEntity, TId>.Get(TId id) => Get(id);
async ValueTask<TEntity> ILookupReferenceStore<TEntity, TId>.GetAsync(TId id) => (TEntity)await GetAsync(id, CacheMode.CacheOnly).ConfigureAwait(false);
}

internal partial class ClientStateManager
{
public ReferenceStore<SocketGlobalUser, IUserModel, ulong, IUser> UserStore;
public ReferenceStore<SocketUser, IUserModel, ulong, IUser> UserStore;
public ReferenceStore<SocketPresence, IPresenceModel, ulong, IPresence> PresenceStore;

private ConcurrentDictionary<ulong, ReferenceStore<SocketGuildUser, IMemberModel, ulong, IGuildUser>> _memberStores;
private ConcurrentDictionary<ulong, ReferenceStore<SocketThreadUser, IThreadMemberModel, ulong, IThreadUser>> _threadMemberStores;
private ConcurrentDictionary<ulong, ReferenceStore<SocketMessage, IMessageModel, ulong, IMessage>> _messageStores;

private SemaphoreSlim _memberStoreLock;
private SemaphoreSlim _messageStoreLock;
private SemaphoreSlim _threadMemberLock;

#region Models
private readonly Dictionary<Type, Func<object>> _defaultModelFactory = new()
{
{ typeof(IUserModel), () => new SocketUser.CacheModel() },
@@ -284,10 +304,45 @@ namespace Discord.WebSocket
{ typeof(ICurrentUserModel), () => new SocketSelfUser.CacheModel() },
{ typeof(IThreadMemberModel), () => new SocketThreadUser.CacheModel() },
{ typeof(IPresenceModel), () => new SocketPresence.CacheModel() },
{ typeof(IActivityModel), () => new SocketPresence.ActivityCacheModel() }
{ typeof(IActivityModel), () => new SocketPresence.ActivityCacheModel() },
{ typeof(IMessageModel), () => new SocketMessage.CacheModel() },
{ typeof(IMessageActivityModel), () => new SocketMessage.CacheModel.MessageActivityModel() },
{ typeof(IMessageComponentModel), () => new SocketMessage.CacheModel.MessageComponentModel() },
{ typeof(IMessageComponentOptionModel), () => new SocketMessage.CacheModel.MessageComponentModel.MessageComponentOptionModel() },
{ typeof(IPartialApplicationModel), () => new SocketMessage.CacheModel.PartialApplicationModel() },
{ typeof(IStickerItemModel), () => new SocketMessage.CacheModel.StickerItemModel() },
{ typeof(IReactionMetadataModel), () => new SocketMessage.CacheModel.ReactionModel() },
{ typeof(IEmbedModel), () => new SocketMessage.CacheModel.EmbedModel() },
{ typeof(IEmbedFieldModel), () => new SocketMessage.CacheModel.EmbedModel.EmbedFieldModel() },
{ typeof(IEmbedMediaModel), () => new SocketMessage.CacheModel.EmbedModel.EmbedMediaModel()}

};

public TModel GetModel<TModel, TFallback>()
where TFallback : class, TModel, new()
where TModel : class
{
return GetModel<TModel>() ?? new TFallback();
}

public TModel GetModel<TModel>()
where TModel : class
{
var type = _cacheProvider.GetModel<TModel>();

if (type != null)
{
if (!type.GetInterfaces().Contains(typeof(TModel)))
throw new InvalidOperationException($"Cannot use {type.Name} as a model for {typeof(TModel).Name}");

return (TModel)Activator.CreateInstance(type);
}
else
return _defaultModelFactory.TryGetValue(typeof(TModel), out var m) ? (TModel)m() : null;
}
#endregion

#region References & Initialization
public void ClearDeadReferences()
{
UserStore.ClearDeadReferences();
@@ -300,6 +355,29 @@ namespace Discord.WebSocket
await PresenceStore.InitializeAsync();
}

private void CreateStores()
{
UserStore = new ReferenceStore<SocketUser, IUserModel, ulong, IUser>(
_cacheProvider,
m => SocketGlobalUser.Create(_client, m),
async (id, options) => await _client.Rest.GetUserAsync(id, options).ConfigureAwait(false),
GetModel<IUserModel>);

PresenceStore = new ReferenceStore<SocketPresence, IPresenceModel, ulong, IPresence>(
_cacheProvider,
m => SocketPresence.Create(_client, m),
(id, options) => Task.FromResult<IPresence>(null),
GetModel<IPresenceModel>);

_memberStores = new();
_threadMemberStores = new();

_threadMemberLock = new(1, 1);
_memberStoreLock = new(1, 1);
}
#endregion

#region Members
public ReferenceStore<SocketGuildUser, IMemberModel, ulong, IGuildUser> GetMemberStore(ulong guildId)
=> TryGetMemberStore(guildId, out var store) ? store : null;

@@ -331,7 +409,9 @@ namespace Discord.WebSocket
_memberStoreLock.Release();
}
}
#endregion

#region Thread Members
public async Task<ReferenceStore<SocketThreadUser, IThreadMemberModel, ulong, IThreadUser>> GetThreadMemberStoreAsync(ulong threadId, ulong guildId)
{
if (_threadMemberStores.TryGetValue(threadId, out var store))
@@ -360,49 +440,40 @@ namespace Discord.WebSocket

public ReferenceStore<SocketThreadUser, IThreadMemberModel, ulong, IThreadUser> GetThreadMemberStore(ulong threadId)
=> _threadMemberStores.TryGetValue(threadId, out var store) ? store : null;
#endregion

public TModel GetModel<TModel, TFallback>()
where TFallback : class, TModel, new()
where TModel : class
{
return GetModel<TModel>() ?? new TFallback();
}
#region Messages
public ReferenceStore<SocketMessage, IMessageModel, ulong, IMessage> GetMessageStore(ulong channelId)
=> TryGetMessageStore(channelId, out var store) ? store : null;

public TModel GetModel<TModel>()
where TModel : class
{
var type = _cacheProvider.GetModel<TModel>();

if (type != null)
{
if (!type.GetInterfaces().Contains(typeof(TModel)))
throw new InvalidOperationException($"Cannot use {type.Name} as a model for {typeof(TModel).Name}");

return (TModel)Activator.CreateInstance(type);
}
else
return _defaultModelFactory.TryGetValue(typeof(TModel), out var m) ? (TModel)m() : null;
}
public bool TryGetMessageStore(ulong channelId, out ReferenceStore<SocketMessage, IMessageModel, ulong, IMessage> store)
=> _messageStores.TryGetValue(channelId, out store);

private void CreateStores()
public async ValueTask<ReferenceStore<SocketMessage, IMessageModel, ulong, IMessage>> GetMessageStoreAsync(ulong channelId)
{
UserStore = new ReferenceStore<SocketGlobalUser, IUserModel, ulong, IUser>(
_cacheProvider,
m => SocketGlobalUser.Create(_client, m),
async (id, options) => await _client.Rest.GetUserAsync(id, options).ConfigureAwait(false),
GetModel<IUserModel>);
if (_messageStores.TryGetValue(channelId, out var store))
return store;

PresenceStore = new ReferenceStore<SocketPresence, IPresenceModel, ulong, IPresence>(
_cacheProvider,
m => SocketPresence.Create(_client, m),
(id, options) => Task.FromResult<IPresence>(null),
GetModel<IPresenceModel>);
await _messageStoreLock.WaitAsync().ConfigureAwait(false);

_memberStores = new();
_threadMemberStores = new();
try
{
store = new ReferenceStore<SocketMessage, IMessageModel, ulong, IMessage>(
_cacheProvider,
m => SocketMessage.Create(_client, m, channelId),
async (id, options) => await _client.Rest.GetMessageAsync(channelId, id).ConfigureAwait(false),
GetModel<IMessageModel>);

_threadMemberLock = new(1, 1);
_memberStoreLock = new(1,1);
await store.InitializeAsync(channelId).ConfigureAwait(false);

_messageStores.TryAdd(channelId, store);
return store;
}
finally
{
_memberStoreLock.Release();
}
}
#endregion
}
}

+ 356
- 57
src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs View File

@@ -5,19 +5,25 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Model = Discord.API.Message;
using Model = Discord.IMessageModel;

namespace Discord.WebSocket
{
/// <summary>
/// Represents a WebSocket-based message.
/// </summary>
public abstract class SocketMessage : SocketEntity<ulong>, IMessage
public abstract class SocketMessage : SocketEntity<ulong>, IMessage, ICached<Model>
{
#region SocketMessage
internal bool IsFreed { get; set; }
private long _timestampTicks;
private readonly List<SocketReaction> _reactions = new List<SocketReaction>();
private ImmutableArray<SocketUser> _userMentions = ImmutableArray.Create<SocketUser>();
private ulong[] _userMentionIds;
private ulong _channelId;
private ulong _guildId;
private ulong _authorId;
private bool _isWebhook;
//private ImmutableArray<SocketUser> _userMentions = ImmutableArray.Create<SocketUser>();

/// <summary>
/// Gets the author of this message.
@@ -54,6 +60,7 @@ namespace Discord.WebSocket
public virtual DateTimeOffset? EditedTimestamp => null;
/// <inheritdoc />
public virtual bool MentionedEveryone => false;
public virtual ulong? ApplicationId { get; private set; }

/// <inheritdoc />
public MessageActivity Activity { get; private set; }
@@ -115,10 +122,13 @@ namespace Discord.WebSocket
/// <summary>
/// Returns the users mentioned in this message.
/// </summary>
/// <remarks>
/// The returned enumerable will preform cache lookups per enumeration.
/// </remarks>
/// <returns>
/// Collection of WebSocket-based users.
/// </returns>
public IReadOnlyCollection<SocketUser> MentionedUsers => _userMentions;
public IEnumerable<SocketUser> MentionedUsers => Discord.StateManager.UserStore.GetEnumerable(_userMentionIds); // TODO: async counterpart?
/// <inheritdoc />
public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks);

@@ -129,6 +139,12 @@ namespace Discord.WebSocket
Author = author;
Source = source;
}

//internal static SocketMessage Create(DiscordSocketClient discord, Model model, ulong channelId)
//{

//}

internal static SocketMessage Create(DiscordSocketClient discord, ClientStateManager state, SocketUser author, ISocketMessageChannel channel, Model model)
{
if (model.Type == MessageType.Default ||
@@ -140,55 +156,52 @@ namespace Discord.WebSocket
else
return SocketSystemMessage.Create(discord, state, author, channel, model);
}
internal virtual void Update(ClientStateManager state, Model model)
internal virtual void Update(Model model)
{
Type = model.Type;

if (model.Timestamp.IsSpecified)
_timestampTicks = model.Timestamp.Value.UtcTicks;

if (model.Content.IsSpecified)
{
Content = model.Content.Value;
}
_timestampTicks = model.Timestamp;
ApplicationId = model.ApplicationId;
Content = model.Content;
_userMentionIds = model.UserMentionIds;

if (model.Application.IsSpecified)
if (model.Application != null)
{
// create a new Application from the API model
Application = new MessageApplication()
{
Id = model.Application.Value.Id,
CoverImage = model.Application.Value.CoverImage,
Description = model.Application.Value.Description,
Icon = model.Application.Value.Icon,
Name = model.Application.Value.Name
Id = model.Application.Id,
CoverImage = model.Application.CoverImage,
Description = model.Application.Description,
Icon = model.Application.Icon,
Name = model.Application.Name
};
}

if (model.Activity.IsSpecified)
if (model.Activity != null)
{
// create a new Activity from the API model
Activity = new MessageActivity()
{
Type = model.Activity.Value.Type.Value,
PartyId = model.Activity.Value.PartyId.GetValueOrDefault()
Type = model.Activity.Type.Value,
PartyId = model.Activity.PartyId
};
}

if (model.Reference.IsSpecified)
if (model.ReferenceMessageId.HasValue)
{
// Creates a new Reference from the API model
Reference = new MessageReference
{
GuildId = model.Reference.Value.GuildId,
InternalChannelId = model.Reference.Value.ChannelId,
MessageId = model.Reference.Value.MessageId
GuildId = model.ReferenceMessageGuildId.ToOptional(),
InternalChannelId = model.ReferenceMessageChannelId.ToOptional(),
MessageId = model.ReferenceMessageId.ToOptional()
};
}

if (model.Components.IsSpecified)
if (model.Components != null && model.Components.Length > 0)
{
Components = model.Components.Value.Select(x => new ActionRowComponent(x.Components.Select<IMessageComponent, IMessageComponent>(y =>
Components = model.Components.Select(x => new ActionRowComponent(x.Components.Select<IMessageComponentModel, IMessageComponent>(y =>
{
switch (y.Type)
{
@@ -236,38 +249,16 @@ namespace Discord.WebSocket
else
Components = new List<ActionRowComponent>();

if (model.UserMentions.IsSpecified)
if (model.InteractionId.HasValue)
{
var value = model.UserMentions.Value;
if (value.Length > 0)
{
var newMentions = ImmutableArray.CreateBuilder<SocketUser>(value.Length);
for (int i = 0; i < value.Length; i++)
{
var val = value[i];
if (val != null)
{
var user = Channel.GetUserAsync(val.Id, CacheMode.CacheOnly).GetAwaiter().GetResult() as SocketUser;
if (user != null)
newMentions.Add(user);
else
newMentions.Add(SocketUnknownUser.Create(Discord, val));
}
}
_userMentions = newMentions.ToImmutable();
}
Interaction = new MessageInteraction<SocketUser>(model.InteractionId.Value,
model.InteractionType.Value,
model.InteractionName,
model.InteractionUserId.Value,
Discord.StateManager.UserStore.Get);
}

if (model.Interaction.IsSpecified)
{
Interaction = new MessageInteraction<SocketUser>(model.Interaction.Value.Id,
model.Interaction.Value.Type,
model.Interaction.Value.Name,
SocketGlobalUser.Create(Discord, model.Interaction.Value.User));
}

if (model.Flags.IsSpecified)
Flags = model.Flags.Value;
Flags = model.Flags;
}

/// <inheritdoc />
@@ -309,7 +300,6 @@ namespace Discord.WebSocket
/// <inheritdoc />
IReadOnlyCollection<IStickerItem> IMessage.Stickers => Stickers;


internal void AddReaction(SocketReaction reaction)
{
_reactions.Add(reaction);
@@ -347,5 +337,314 @@ namespace Discord.WebSocket
public IAsyncEnumerable<IReadOnlyCollection<IUser>> GetReactionUsersAsync(IEmote emote, int limit, RequestOptions options = null)
=> MessageHelper.GetReactionUsersAsync(this, emote, limit, Discord, options);
#endregion

#region Cache

internal class CacheModel : Model
{
public MessageType Type { get; set; }
public ulong ChannelId { get; set; }
public ulong? GuildId { get; set; }
public ulong AuthorId { get; set; }
public bool IsWebhookMessage { get; set; }
public string Content { get; set; }
public long Timestamp { get; set; }
public long? EditedTimestamp { get; set; }
public bool IsTextToSpeech { get; set; }
public bool MentionEveryone { get; set; }
public ulong[] UserMentionIds { get; set; }
public AttachmentModel[] Attachments { get; set; }
public EmbedModel[] Embeds { get; set; }
public ReactionModel[] Reactions { get; set; } // TODO: seperate store?
public bool Pinned { get; set; }
public MessageActivityModel Activity { get; set; }
public PartialApplicationModel Application { get; set; }
public ulong? ApplicationId { get; set; }
public ulong? ReferenceMessageId { get; set; }
public ulong? ReferenceMessageChannelId { get; set; }
public ulong? ReferenceMessageGuildId { get; set; }
public MessageFlags Flags { get; set; }
public ulong? InteractionId { get; set; }
public string InteractionName { get; set; }
public InteractionType? InteractionType { get; set; }
public ulong? InteractionUserId { get; set; }
public MessageComponentModel[] Components { get; set; }
public StickerItemModel[] Stickers { get; set; }
public ulong Id { get; set; }

internal class AttachmentModel : IAttachmentModel
{
public string FileName { get; set; }
public string Description { get; set; }
public string ContentType { get; set; }
public int Size { get; set; }
public string Url { get; set; }
public string ProxyUrl { get; set; }
public int? Height { get; set; }
public int? Width { get; set; }
public bool Ephemeral { get; set; }
public ulong Id { get; set; }
}
internal class EmbedModel : IEmbedModel
{
public string Title { get; set; }
public EmbedType Type { get; set; }
public string Description { get; set; }
public string Url { get; set; }
public long? Timestamp { get; set; }
public uint? Color { get; set; }
public string FooterText { get; set; }
public string FooterIconUrl { get; set; }
public string FooterProxyUrl { get; set; }
public string ProviderName { get; set; }
public string ProviderUrl { get; set; }
public string AuthorName { get; set; }
public string AuthorUrl { get; set; }
public string AuthorIconUrl { get; set; }
public string AuthorProxyIconUrl { get; set; }
public EmbedMediaModel Image { get; set; }
public EmbedMediaModel Thumbnail { get; set; }
public EmbedMediaModel Video { get; set; }
public EmbedFieldModel[] Fields { get; set; }

IEmbedMediaModel IEmbedModel.Image { get => Image; set => Image = value.InterfaceCopy<EmbedMediaModel>(); }
IEmbedMediaModel IEmbedModel.Thumbnail { get => Thumbnail; set => Thumbnail = value.InterfaceCopy<EmbedMediaModel>(); }
IEmbedMediaModel IEmbedModel.Video { get => Video; set => Video = value.InterfaceCopy<EmbedMediaModel>(); }
IEmbedFieldModel[] IEmbedModel.Fields { get => Fields; set => value?.Select(x => x.InterfaceCopy<EmbedFieldModel>()); }

internal class EmbedMediaModel : IEmbedMediaModel
{
public string Url { get; set; }
public string ProxyUrl { get; set; }
public int? Height { get; set; }
public int? Width { get; set; }
}
internal class EmbedFieldModel : IEmbedFieldModel
{
public string Name { get; set; }
public string Value { get; set; }
public bool Inline { get; set; }
}
}
internal class ReactionModel : IReactionMetadataModel
{
public IEmojiModel Emoji { get; set; }
public ulong[] Users { get; set; }
}
internal class MessageActivityModel : IMessageActivityModel
{
public MessageActivityType? Type { get; set; }
public string PartyId { get; set; }
}
internal class PartialApplicationModel : IPartialApplicationModel
{
public string Name { get; set; }
public string Icon { get; set; }
public string Description { get; set; }
public string CoverImage { get; set; }
public ulong Id { get; set; }
}
internal class MessageComponentModel : IMessageComponentModel
{
public ComponentType Type { get; set; }
public string CustomId { get; set; }
public bool? Disabled { get; set; }
public ButtonStyle? Style { get; set; }
public string Label { get; set; }
public ulong? EmojiId { get; set; }
public string EmojiName { get; set; }
public bool? EmojiAnimated { get; set; }
public string Url { get; set; }
public MessageComponentOptionModel[] Options { get; set; }
public string Placeholder { get; set; }
public int? MinValues { get; set; }
public int? MaxValues { get; set; }
public MessageComponentModel[] Components { get; set; }
public int? MinLength { get; set; }
public int? MaxLength { get; set; }
public bool? Required { get; set; }
public string Value { get; set; }
internal class MessageComponentOptionModel : IMessageComponentOptionModel
{
public string Label { get; set; }
public string Value { get; set; }
public string Description { get; set; }
public ulong? EmojiId { get; set; }
public string EmojiName { get; set; }
public bool? EmojiAnimated { get; set; }
public bool? Default { get; set; }
}

IMessageComponentOptionModel[] IMessageComponentModel.Options { get => Options; set => Options = value.Select(x => x.InterfaceCopy(new MessageComponentOptionModel())).ToArray(); }
IMessageComponentModel[] IMessageComponentModel.Components { get => Components; set => Components = value.Select(x => x.InterfaceCopy(new MessageComponentModel())).ToArray(); }
}
internal class StickerItemModel : IStickerItemModel
{
public ulong Id { get; set; }
public string Name { get; set; }
public StickerFormatType Format { get; set; }
}

IAttachmentModel[] Model.Attachments { get => Attachments; set => Attachments = value.Select(x => x.InterfaceCopy<AttachmentModel>()).ToArray(); }
IEmbedModel[] Model.Embeds { get => Embeds; set => Embeds = value.Select(x => x.InterfaceCopy<EmbedModel>()).ToArray(); }
IReactionMetadataModel[] Model.Reactions { get => Reactions; set => Reactions = value.Select(x => x.InterfaceCopy<ReactionModel>()).ToArray(); }
IMessageActivityModel Model.Activity { get => Activity; set => Activity = value.InterfaceCopy<MessageActivityModel>(); }
IPartialApplicationModel Model.Application { get => Application; set => Application = value.InterfaceCopy<PartialApplicationModel>(); }
IMessageComponentModel[] Model.Components { get => Components; set => Components = value.Select(x => x.InterfaceCopy<MessageComponentModel>()).ToArray(); }
IStickerItemModel[] Model.Stickers { get => Stickers; set => Stickers = value.Select(x => x.InterfaceCopy<StickerItemModel>()).ToArray(); }
}

internal virtual Model ToModel()
{
var model = Discord.StateManager.GetModel<Model>();
model.Content = Content;
model.Type = Type;
model.ChannelId = _channelId;
model.GuildId = _guildId;
model.AuthorId = _authorId;
model.IsWebhookMessage = _isWebhook;
model.Timestamp = _timestampTicks;
model.IsTextToSpeech = IsTTS;
model.MentionEveryone = MentionedEveryone;
model.UserMentionIds = _userMentionIds;
model.ApplicationId = ApplicationId;
model.Flags = Flags ?? MessageFlags.None;
model.Id = Id;

if(Interaction != null)
{
model.InteractionName = Interaction.Name;
model.InteractionId = Interaction.Id;
model.InteractionType = Interaction.Type;
model.InteractionUserId = Interaction.UserId;
}

if(Reference != null)
{
model.ReferenceMessageId = Reference.MessageId.ToNullable();
model.ReferenceMessageGuildId = Reference.GuildId.ToNullable();
model.ReferenceMessageChannelId = Reference.ChannelId;
}

model.Attachments = Attachments.Select(x =>
{
var attachmentModel = Discord.StateManager.GetModel<IAttachmentModel>();
attachmentModel.Width = x.Width;
attachmentModel.Height = x.Height;
attachmentModel.Size = x.Size;
attachmentModel.Description = x.Description;
attachmentModel.Ephemeral = x.Ephemeral;
attachmentModel.FileName = x.Filename;
attachmentModel.Url = x.Url;
attachmentModel.ContentType = x.ContentType;
attachmentModel.Id = x.Id;
attachmentModel.ProxyUrl = x.ProxyUrl;

return attachmentModel;
}).ToArray();

model.Embeds = Embeds.Select(x =>
{
var embedModel = Discord.StateManager.GetModel<IEmbedModel>();

embedModel.AuthorName = x.Author?.Name;
embedModel.AuthorProxyIconUrl = x.Author?.ProxyIconUrl;
embedModel.AuthorIconUrl = x.Author?.IconUrl;
embedModel.AuthorUrl = x.Author?.Url;

embedModel.Color = x.Color;
embedModel.Description = x.Description;
embedModel.Title = x.Title;
embedModel.Timestamp = x.Timestamp?.UtcTicks;
embedModel.Type = x.Type;
embedModel.Url = x.Url;

embedModel.ProviderName = x.Provider?.Name;
embedModel.ProviderUrl = x.Provider?.Url;

embedModel.FooterIconUrl = x.Footer?.IconUrl;
embedModel.FooterProxyUrl = x.Footer?.ProxyUrl;
embedModel.FooterText = x.Footer?.Text;

var image = Discord.StateManager.GetModel<IEmbedMediaModel>();
image.Width = x.Image?.Width;
image.Height = x.Image?.Height;
image.Url = x.Image?.Url;
image.ProxyUrl = x.Image?.ProxyUrl;

embedModel.Image = image;

var thumbnail = Discord.StateManager.GetModel<IEmbedMediaModel>();
thumbnail.Width = x.Thumbnail?.Width;
thumbnail.Height = x.Thumbnail?.Height;
thumbnail.Url = x.Thumbnail?.Url;
thumbnail.ProxyUrl = x.Thumbnail?.ProxyUrl;

embedModel.Thumbnail = thumbnail;

var video = Discord.StateManager.GetModel<IEmbedMediaModel>();
video.Width = x.Video?.Width;
video.Height = x.Video?.Height;
video.Url = x.Video?.Url;

embedModel.Video = video;

embedModel.Fields = x.Fields.Select(x =>
{
var fieldModel = Discord.StateManager.GetModel<IEmbedFieldModel>();
fieldModel.Name = x.Name;
fieldModel.Value = x.Value;
fieldModel.Inline = x.Inline;
return fieldModel;
}).ToArray();

return embedModel;
}).ToArray();

model.Reactions = _reactions.GroupBy(x => x.Emote).Select(x =>
{
var reactionMetadataModel = Discord.StateManager.GetModel<IReactionMetadataModel>();
reactionMetadataModel.Emoji = x.Key.ToModel(Discord.StateManager.GetModel<IEmojiModel>());
reactionMetadataModel.Users = x.Select(x => x.UserId).ToArray();
return reactionMetadataModel;
}).ToArray();

var activityModel = Discord.StateManager.GetModel<IMessageActivityModel>();
activityModel.PartyId = Activity?.PartyId;
activityModel.Type = Activity?.Type;
model.Activity = activityModel;

var applicationModel = Discord.StateManager.GetModel<IPartialApplicationModel>();
applicationModel.Description = Application.Description;
applicationModel.Name = Application.Name;
applicationModel.CoverImage = Application.CoverImage;
applicationModel.Id = Application.Id;
applicationModel.Icon = Application.Icon;
model.Application = applicationModel;

return model;
}

~SocketMessage() => Dispose();
public void Dispose()
{
if (IsFreed)
return;

IsFreed = true;

GC.SuppressFinalize(this);

if (Discord.StateManager.TryGetMessageStore(Channel.Id, out var store))
store.RemoveReference(Id);
}


void ICached<Model>.Update(Model model) => Update(model);
Model ICached<Model>.ToModel() => ToModel();

bool ICached.IsFreed => IsFreed;
#endregion
}
}

+ 33
- 22
src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs View File

@@ -20,12 +20,19 @@ namespace Discord.WebSocket
/// </returns>
public ulong UserId { get; }
/// <summary>
/// Gets the ID of the message that has been reacted to.
/// </summary>
/// <returns>
/// A message snowflake identifier associated with the message.
/// </returns>
public ulong MessageId { get; }
/// <summary>
/// Gets the user who added the reaction if possible.
/// </summary>
/// <remarks>
/// <para>
/// This property attempts to retrieve a WebSocket-cached user that is responsible for this reaction from
/// the client. In other words, when the user is not in the WebSocket cache, this property may not
/// the client. In other words, when the user is not in the cache, this property may not
/// contain a value, leaving the only identifiable information to be
/// <see cref="Discord.WebSocket.SocketReaction.UserId" />.
/// </para>
@@ -35,25 +42,16 @@ namespace Discord.WebSocket
/// </para>
/// </remarks>
/// <returns>
/// A user object where possible; a value is not always returned.
/// A lazily-cached user object.
/// </returns>
/// <seealso cref="Optional{T}"/>
public Optional<IUser> User { get; }
/// <summary>
/// Gets the ID of the message that has been reacted to.
/// </summary>
/// <returns>
/// A message snowflake identifier associated with the message.
/// </returns>
public ulong MessageId { get; }
public LazyCached<SocketUser> User { get; }
/// <summary>
/// Gets the message that has been reacted to if possible.
/// </summary>
/// <returns>
/// A WebSocket-based message where possible; a value is not always returned.
/// A lazily-cached message.
/// </returns>
/// <seealso cref="Optional{T}"/>
public Optional<SocketUserMessage> Message { get; }
public LazyCached<SocketMessage> Message { get; }
/// <summary>
/// Gets the channel where the reaction takes place in.
/// </summary>
@@ -64,16 +62,26 @@ namespace Discord.WebSocket
/// <inheritdoc />
public IEmote Emote { get; }

internal SocketReaction(ISocketMessageChannel channel, ulong messageId, Optional<SocketUserMessage> message, ulong userId, Optional<IUser> user, IEmote emoji)
internal SocketReaction(ISocketMessageChannel channel, ulong messageId, Optional<SocketUserMessage> message, ulong userId, Optional<SocketUser> user, IEmote emoji)
{
Channel = channel;
var client = ((SocketChannel)channel).Discord;

MessageId = messageId;
Message = message;
UserId = userId;
User = user;

Channel = channel;

Message = message.IsSpecified
? new LazyCached<SocketMessage>(message.Value)
: new LazyCached<SocketMessage>(messageId, client.StateManager.GetMessageStore(channel.Id));

User = user.IsSpecified
? new LazyCached<SocketUser>(user.Value)
: new LazyCached<SocketUser>(userId, client.StateManager.UserStore);

Emote = emoji;
}
internal static SocketReaction Create(Model model, ISocketMessageChannel channel, Optional<SocketUserMessage> message, Optional<IUser> user)
internal static SocketReaction Create(Model model, ISocketMessageChannel channel, Optional<SocketUserMessage> message, Optional<SocketUser> user)
{
IEmote emote;
if (model.Emoji.Id.HasValue)
@@ -86,11 +94,14 @@ namespace Discord.WebSocket
/// <inheritdoc />
public override bool Equals(object other)
{
if (other == null) return false;
if (other == this) return true;
if (other == null)
return false;
if (other == this)
return true;

var otherReaction = other as SocketReaction;
if (otherReaction == null) return false;
if (otherReaction == null)
return false;

return UserId == otherReaction.UserId && MessageId == otherReaction.MessageId && Emote.Equals(otherReaction.Emote);
}


+ 22
- 50
src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs View File

@@ -5,7 +5,7 @@ using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Model = Discord.API.Message;
using Model = Discord.IMessageModel;

namespace Discord.WebSocket
{
@@ -17,11 +17,12 @@ namespace Discord.WebSocket
{
private bool _isMentioningEveryone, _isTTS, _isPinned;
private long? _editedTimestampTicks;
private IUserMessage _referencedMessage;
private ImmutableArray<Attachment> _attachments = ImmutableArray.Create<Attachment>();
private ImmutableArray<Embed> _embeds = ImmutableArray.Create<Embed>();
private ImmutableArray<ITag> _tags = ImmutableArray.Create<ITag>();
private ImmutableArray<SocketRole> _roleMentions = ImmutableArray.Create<SocketRole>();
private ulong[] _roleMentions;
private ulong? _referencedMessageId;
//private ImmutableArray<SocketRole> _roleMentions = ImmutableArray.Create<SocketRole>();
private ImmutableArray<SocketSticker> _stickers = ImmutableArray.Create<SocketSticker>();

/// <inheritdoc />
@@ -56,30 +57,26 @@ namespace Discord.WebSocket
internal new static SocketUserMessage Create(DiscordSocketClient discord, ClientStateManager state, SocketUser author, ISocketMessageChannel channel, Model model)
{
var entity = new SocketUserMessage(discord, model.Id, channel, author, MessageHelper.GetSource(model));
entity.Update(state, model);
entity.Update(model);
return entity;
}

internal override void Update(ClientStateManager state, Model model)
internal override void Update(Model model)
{
base.Update(state, model);
base.Update(model);

SocketGuild guild = (Channel as SocketGuildChannel)?.Guild;

if (model.IsTextToSpeech.IsSpecified)
_isTTS = model.IsTextToSpeech.Value;
if (model.Pinned.IsSpecified)
_isPinned = model.Pinned.Value;
if (model.EditedTimestamp.IsSpecified)
_editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks;
if (model.MentionEveryone.IsSpecified)
_isMentioningEveryone = model.MentionEveryone.Value;
if (model.RoleMentions.IsSpecified)
_roleMentions = model.RoleMentions.Value.Select(x => guild.GetRole(x)).ToImmutableArray();

if (model.Attachments.IsSpecified)
_isTTS = model.IsTextToSpeech;
_isPinned = model.Pinned;
_editedTimestampTicks = model.EditedTimestamp;
_isMentioningEveryone = model.MentionEveryone;
_roleMentions = model.RoleMentionIds;
_referencedMessageId = model.ReferenceMessageId;

if (model.Attachments != null && model.Attachments.Length > 0)
{
var value = model.Attachments.Value;
var value = model.Attachments;
if (value.Length > 0)
{
var attachments = ImmutableArray.CreateBuilder<Attachment>(value.Length);
@@ -91,9 +88,9 @@ namespace Discord.WebSocket
_attachments = ImmutableArray.Create<Attachment>();
}

if (model.Embeds.IsSpecified)
if (model.Embeds != null && model.Embeds.Length > 0)
{
var value = model.Embeds.Value;
var value = model.Embeds;
if (value.Length > 0)
{
var embeds = ImmutableArray.CreateBuilder<Embed>(value.Length);
@@ -105,41 +102,16 @@ namespace Discord.WebSocket
_embeds = ImmutableArray.Create<Embed>();
}

if (model.Content.IsSpecified)
if (model.Content != null)
{
var text = model.Content.Value;
var text = model.Content;
_tags = MessageHelper.ParseTags(text, Channel, guild, MentionedUsers);
model.Content = text;
}

if (model.ReferencedMessage.IsSpecified && model.ReferencedMessage.Value != null)
{
var refMsg = model.ReferencedMessage.Value;
ulong? webhookId = refMsg.WebhookId.ToNullable();
SocketUser refMsgAuthor = null;
if (refMsg.Author.IsSpecified)
{
if (guild != null)
{
if (webhookId != null)
refMsgAuthor = SocketWebhookUser.Create(guild, refMsg.Author.Value, webhookId.Value);
else
refMsgAuthor = guild.GetUser(refMsg.Author.Value.Id);
}
else
refMsgAuthor = (Channel as SocketChannel).GetUser(refMsg.Author.Value.Id);
if (refMsgAuthor == null)
refMsgAuthor = SocketUnknownUser.Create(Discord, refMsg.Author.Value);
}
else
// Message author wasn't specified in the payload, so create a completely anonymous unknown user
refMsgAuthor = new SocketUnknownUser(Discord, id: 0);
_referencedMessage = SocketUserMessage.Create(Discord, state, refMsgAuthor, Channel, refMsg);
}

if (model.StickerItems.IsSpecified)
if (model.Stickers != null && model.Stickers.Length > 0)
{
var value = model.StickerItems.Value;
var value = model.Stickers;
if (value.Length > 0)
{
var stickers = ImmutableArray.CreateBuilder<SocketSticker>(value.Length);


+ 2
- 2
src/Discord.Net.WebSocket/Entities/Stickers/SocketUnknownSticker.cs View File

@@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using Model = Discord.API.StickerItem;
using Model = Discord.IStickerItemModel;

namespace Discord.WebSocket
{
@@ -47,7 +47,7 @@ namespace Discord.WebSocket
internal void Update(Model model)
{
Name = model.Name;
Format = model.FormatType;
Format = model.Format;
}

/// <summary>


+ 18
- 4
src/Discord.Net.WebSocket/Extensions/CacheModelExtensions.cs View File

@@ -8,19 +8,26 @@ namespace Discord.WebSocket
{
internal static class CacheModelExtensions
{
public static TDest ToSpecifiedModel<TId, TDest>(this IEntityModel<TId> source, TDest dest)
where TId : IEquatable<TId>
where TDest : IEntityModel<TId>
public static TDest InterfaceCopy<TDest>(this object source)
where TDest : class, new()
=> source.InterfaceCopy(new TDest());

public static TDest InterfaceCopy<TSource, TDest>(this TSource source, TDest dest)
where TSource : class
where TDest : class
{
if (source == null || dest == null)
throw new ArgumentNullException(source == null ? nameof(source) : nameof(dest));

if (source == null || dest == null)
throw new ArgumentNullException(source == null ? nameof(source) : nameof(dest));

// get the shared model interface
var sourceType = source.GetType();
var destType = dest.GetType();

if (sourceType == destType)
return (TDest)source;
return source as TDest;

List<Type> sharedInterfaceModels = new();

@@ -52,5 +59,12 @@ namespace Discord.WebSocket

return dest;
}

public static TDest ToSpecifiedModel<TId, TDest>(this IEntityModel<TId> source, TDest dest)
where TId : IEquatable<TId>
where TDest : class, IEntityModel<TId>
{
return source.InterfaceCopy(dest);
}
}
}

+ 16
- 0
src/Discord.Net.WebSocket/Extensions/EntityCacheExtensions.cs View File

@@ -0,0 +1,16 @@
using Discord.WebSocket;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
public static class EntityCacheExtensions
{
public static ValueTask<IUser> GetUserAsync(this MessageInteraction<SocketUser> interaction, DiscordSocketClient client,
CacheMode mode, RequestOptions options = null)
=> client.StateManager.UserStore.GetAsync(interaction.UserId, mode, options);
}
}

+ 12
- 0
src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs View File

@@ -7,6 +7,17 @@ namespace Discord.WebSocket
{
internal static class EntityExtensions
{
#region Emotes
public static IEmote ToEntity(this IEmojiModel model)
{
if (model.Id.HasValue)
return new Emote(model.Id.Value, model.Name, model.IsAnimated);
else
return new Emoji(model.Name);
}
#endregion

#region Activity
public static IActivity ToEntity(this IActivityModel model)
{
#region Custom Status Game
@@ -147,5 +158,6 @@ namespace Discord.WebSocket
{
return new GameTimestamps(model.Start.ToNullable(), model.End.ToNullable());
}
#endregion
}
}

Loading…
Cancel
Save